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

pull/1/head
yflory 8 years ago
commit fc760b4820

@ -10,3 +10,4 @@ NetFluxWebsocketSrv.js
NetFluxWebsocketServer.js NetFluxWebsocketServer.js
WebRTCSrv.js WebRTCSrv.js
www/common/media-tag.js www/common/media-tag.js
www/scratch

@ -376,6 +376,23 @@ var resetUserPins = function (store, Sessions, publicKey, channelList, cb) {
}); });
}; };
var getLimit = function (cb) {
};
var createBlobStaging = function (cb) {
};
var createBlobStore = function (cb) {
};
var upload = function (store, Sessions, publicKey, cb) {
};
/*::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...
@ -428,7 +445,6 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
return void respond('INVALID_MESSAGE_OR_PUBLIC_KEY'); return void respond('INVALID_MESSAGE_OR_PUBLIC_KEY');
} }
if (checkSignature(serialized, signature, publicKey) !== true) { if (checkSignature(serialized, signature, publicKey) !== true) {
return void respond("INVALID_SIGNATURE_OR_PUBLIC_KEY"); return void respond("INVALID_SIGNATURE_OR_PUBLIC_KEY");
} }
@ -459,7 +475,8 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
return resetUserPins(store, Sessions, safeKey, msg[1], function (e, hash) { return resetUserPins(store, Sessions, safeKey, msg[1], function (e, hash) {
return void Respond(e, hash); return void Respond(e, hash);
}); });
case 'PIN': case 'PIN': // TODO don't pin if over the limit
// if over, send error E_OVER_LIMIT
return pinChannel(store, Sessions, safeKey, msg[1], function (e, hash) { return pinChannel(store, Sessions, safeKey, msg[1], function (e, hash) {
Respond(e, hash); Respond(e, hash);
}); });
@ -471,13 +488,17 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
return void getHash(store, Sessions, safeKey, function (e, hash) { return void getHash(store, Sessions, safeKey, function (e, hash) {
Respond(e, hash); Respond(e, hash);
}); });
case 'GET_TOTAL_SIZE': case 'GET_TOTAL_SIZE': // TODO cache this, since it will get called quite a bit
return getTotalSize(store, ctx.store, Sessions, safeKey, function (e, size) { return getTotalSize(store, ctx.store, Sessions, safeKey, function (e, size) {
if (e) { return void Respond(e); } if (e) { return void Respond(e); }
Respond(e, size); Respond(e, size);
}); });
case 'GET_FILE_SIZE': case 'GET_FILE_SIZE':
return void getFileSize(ctx.store, msg[1], Respond); return void getFileSize(ctx.store, msg[1], Respond);
case 'GET_LIMIT': // TODO implement this and cache it per-user
return void getLimit(function (e, limit) {
Respond('NOT_IMPLEMENTED');
});
case 'GET_MULTIPLE_FILE_SIZE': case 'GET_MULTIPLE_FILE_SIZE':
return void getMultipleFileSize(ctx.store, msg[1], function (e, dict) { return void getMultipleFileSize(ctx.store, msg[1], function (e, dict) {
if (e) { return void Respond(e); } if (e) { return void Respond(e); }

@ -266,5 +266,9 @@ Version 2
return hex; return hex;
}; };
var getBlobPath = Hash.getBlobPathFromHex = function (id) {
return '/blob/' + id.slice(0,2) + '/' + id;
};
return Hash; return Hash;
}); });

@ -73,6 +73,7 @@ define([
var hrefToHexChannelId = common.hrefToHexChannelId = Hash.hrefToHexChannelId; var hrefToHexChannelId = common.hrefToHexChannelId = Hash.hrefToHexChannelId;
var parseHash = common.parseHash = Hash.parseHash; var parseHash = common.parseHash = Hash.parseHash;
var getRelativeHref = common.getRelativeHref = Hash.getRelativeHref; var getRelativeHref = common.getRelativeHref = Hash.getRelativeHref;
common.getBlobPathFromHex = Hash.getBlobPathFromHex;
common.getEditHashFromKeys = Hash.getEditHashFromKeys; common.getEditHashFromKeys = Hash.getEditHashFromKeys;
common.getViewHashFromKeys = Hash.getViewHashFromKeys; common.getViewHashFromKeys = Hash.getViewHashFromKeys;

@ -0,0 +1,182 @@
define([
'/bower_components/tweetnacl/nacl-fast.min.js',
], function () {
var Nacl = window.nacl;
var PARANOIA = true;
var plainChunkLength = 128 * 1024;
var cypherChunkLength = 131088;
var slice = function (A) {
return Array.prototype.slice.call(A);
};
var createNonce = function () {
return new Uint8Array(new Array(24).fill(0));
};
var increment = function (N) {
var l = N.length;
while (l-- > 1) {
if (PARANOIA) {
if (typeof(N[l]) !== 'number') {
throw new Error('E_UNSAFE_TYPE');
}
if (N[l] > 255) {
throw new Error('E_OUT_OF_BOUNDS');
}
}
/* jshint probably suspects this is unsafe because we lack types
but as long as this is only used on nonces, it should be safe */
if (N[l] !== 255) { return void N[l]++; } // jshint ignore:line
N[l] = 0;
// you don't need to worry about this running out.
// you'd need a REAAAALLY big file
if (l === 0) { return true; }
}
};
var joinChunks = function (chunks) {
return new Uint8Array(chunks.reduce(function (A, B) {
return slice(A).concat(slice(B));
}, []));
};
var padChunk = function (A) {
var padding;
if (A.length === plainChunkLength) { return A; }
if (A.length < plainChunkLength) {
padding = new Array(plainChunkLength - A.length).fill(32);
return A.concat(padding);
}
if (A.length > plainChunkLength) {
// how many times larger is it?
var chunks = Math.ceil(A.length / plainChunkLength);
padding = new Array((plainChunkLength * chunks) - A.length).fill(32);
return A.concat(padding);
}
};
var decrypt = function (u8, key, cb) {
var nonce = createNonce();
var i = 0;
var takeChunk = function () {
var start = i * cypherChunkLength;
var end = start + cypherChunkLength;
i++;
var box = new Uint8Array(u8.subarray(start, end));
// decrypt the chunk
var plaintext = Nacl.secretbox.open(box, nonce, key);
// TODO handle nonce-too-large-error
increment(nonce);
return plaintext;
};
var buffer = '';
var res = {
metadata: undefined,
};
// decrypt metadata
var chunk;
for (; !res.metadata && i * cypherChunkLength < u8.length;) {
chunk = takeChunk();
buffer += Nacl.util.encodeUTF8(chunk);
try {
res.metadata = JSON.parse(buffer);
//console.log(res.metadata);
} catch (e) {
console.log('buffering another chunk for metadata');
}
}
if (!res.metadata) {
return void setTimeout(function () {
cb('NO_METADATA');
});
}
var fail = function () {
cb("DECRYPTION_ERROR");
};
var chunks = [];
// decrypt file contents
for (;i * cypherChunkLength < u8.length;) {
chunk = takeChunk();
if (!chunk) {
return window.setTimeout(fail);
}
chunks.push(chunk);
}
// send chunks
res.content = joinChunks(chunks);
cb(void 0, res);
};
// 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 nonce = createNonce();
// encode metadata
var metaBuffer = Array.prototype.slice
.call(Nacl.util.decodeUTF8(JSON.stringify(metadata)));
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++) {
start = i * plainChunkLength;
end = start + plainChunkLength;
part = new Uint8Array(u8.subarray(start, end));
box = Nacl.secretbox(part, nonce, key);
chunks.push(box);
increment(nonce);
}
// TODO do something with the chunks...
};
return {
decrypt: decrypt,
encrypt: encrypt,
};
});

@ -14,14 +14,44 @@
padding: 0px; padding: 0px;
display: inline-block; display: inline-block;
} }
media-tag * { #file {
max-width: 100%; display: block;
height: 300px;
width: 300px;
border: 2px solid black;
margin: 50px;
} }
.inputfile {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1;
}
.inputfile + label {
border: 2px solid black;
display: block;
height: 500px;
width: 500px;
background-color: rgba(50, 50, 50, .10);
margin: 50px;
}
.inputfile:focus + label,
.inputfile + label:hover {
background-color: rgba(50, 50, 50, 0.30);
}
</style> </style>
</head> </head>
<body> <body>
<div id="toolbar" class="toolbar-container"></div> <div id="toolbar" class="toolbar-container"></div>
<media-tag id="encryptedFile" data-attr-width="4000" data-attr-height="1500"></media-tag> <div id="upload-form" style="display: none;">
<input type="file" name="file" id="file" class="inputfile" />
<label for="file">Choose a file</label>
</div>
</body> </body>
</html> </html>

@ -6,12 +6,14 @@ define([
'/common/cryptpad-common.js', '/common/cryptpad-common.js',
'/common/visible.js', '/common/visible.js',
'/common/notify.js', '/common/notify.js',
'/file/file-crypto.js',
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
'/bower_components/file-saver/FileSaver.min.js', '/bower_components/file-saver/FileSaver.min.js',
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify) { ], function ($, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify, FileCrypto) {
var Messages = Cryptpad.Messages; var Messages = Cryptpad.Messages;
var saveAs = window.saveAs; var saveAs = window.saveAs;
//window.Nacl = window.nacl; var Nacl = window.nacl;
$(function () { $(function () {
var ifrw = $('#pad-iframe')[0].contentWindow; var ifrw = $('#pad-iframe')[0].contentWindow;
@ -19,18 +21,40 @@ define([
Cryptpad.addLoadingScreen(); Cryptpad.addLoadingScreen();
var fetch = function (src, cb) {
var xhr = new XMLHttpRequest();
xhr.open("GET", src, true);
xhr.responseType = "arraybuffer";
xhr.onload = function (e) {
return void cb(void 0, new Uint8Array(xhr.response));
};
xhr.send(null);
};
var upload = function (blob, id, key) {
Cryptpad.alert("UPLOAD IS NOT IMPLEMENTED YET");
};
var myFile;
var myDataType;
var uploadMode = false;
var andThen = function () { var andThen = function () {
var $bar = $iframe.find('.toolbar-container'); var $bar = $iframe.find('.toolbar-container');
var secret = Cryptpad.getSecrets();
if (!secret.keys) { throw new Error("You need a hash"); } // TODO
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var fileId = secret.channel;
var hexFileName = Cryptpad.base64ToHex(fileId);
var type = "image/png";
// Test hash: // Test hash:
// #/2/K6xWU-LT9BJHCQcDCT-DcQ/TBo77200c0e-FdldQFcnQx4Y/ // #/2/K6xWU-LT9BJHCQcDCT-DcQ/TBo77200c0e-FdldQFcnQx4Y/
var secret;
var hexFileName;
if (window.location.hash) {
secret = Cryptpad.getSecrets();
if (!secret.keys) { throw new Error("You need a hash"); } // TODO
hexFileName = Cryptpad.base64ToHex(secret.channel);
} else {
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);
@ -67,45 +91,76 @@ define([
var exportFile = function () { var exportFile = function () {
var suggestion = document.title; var suggestion = document.title;
Cryptpad.prompt(Messages.exportPrompt, Cryptpad.prompt(Messages.exportPrompt,
Cryptpad.fixFileName(suggestion) + '.html', function (filename) { Cryptpad.fixFileName(suggestion), function (filename) {
if (!(typeof(filename) === 'string' && filename)) { return; } if (!(typeof(filename) === 'string' && filename)) { return; }
//var blob = new Blob([html], {type: "text/html;charset=utf-8"}); var blob = new Blob([myFile], {type: myDataType});
saveAs(blob, filename); saveAs(blob, filename);
}); });
}; };
var $mt = $iframe.find('#encryptedFile'); var displayed = ['useradmin', 'newpad', 'limit'];
$mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName); if (secret && hexFileName) {
$mt.attr('data-crypto-key', cryptKey); displayed.push('share');
$mt.attr('data-type', type); }
require(['/common/media-tag.js'], function (MediaTag) { var configTb = {
var configTb = { displayed: displayed,
displayed: ['useradmin', 'share', 'newpad'], ifrw: ifrw,
ifrw: ifrw, common: Cryptpad,
common: Cryptpad, title: {
title: { onRename: renameCb,
onRename: renameCb, defaultName: defaultName,
defaultName: defaultName, suggestName: suggestName
suggestName: suggestName },
}, share: {
share: { secret: secret,
secret: secret, channel: hexFileName
channel: hexFileName }
} };
}; Toolbar.create($bar, null, null, null, null, configTb);
Toolbar.create($bar, null, null, null, null, configTb); var $rightside = $bar.find('.' + Toolbar.constants.rightside);
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
var $export = Cryptpad.createButton('export', true, {}, exportFile);
var $export = Cryptpad.createButton('export', true, {}, exportFile); $rightside.append($export);
$rightside.append($export);
updateTitle(Cryptpad.initialName || getTitle() || defaultName);
updateTitle(Cryptpad.initialName || getTitle() || defaultName);
if (!uploadMode) {
var src = Cryptpad.getBlobPathFromHex(hexFileName);
return fetch(src, function (e, u8) {
// now decrypt the u8
if (e) { return window.alert('error'); }
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var key = Nacl.util.decodeBase64(cryptKey);
FileCrypto.decrypt(u8, key, function (e, data) {
console.log(data);
var title = document.title = data.metadata.filename;
myFile = data.content;
myDataType = data.metadata.type;
updateTitle(title || defaultName);
Cryptpad.removeLoadingScreen();
});
});
}
var mt = MediaTag($mt[0]); var $form = $iframe.find('#upload-form');
$form.css({
display: 'block',
});
Cryptpad.removeLoadingScreen(); 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.readAsText(file);
}); });
// we're in upload mode
Cryptpad.removeLoadingScreen();
}; };
Cryptpad.ready(function (err, anv) { Cryptpad.ready(function (err, anv) {

Loading…
Cancel
Save