Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging
commit
4335b050af
|
@ -37,5 +37,8 @@ define(function() {
|
|||
|
||||
config.enableHistory = true;
|
||||
|
||||
//config.enablePinLimit = true;
|
||||
//config.pinLimit = 1000;
|
||||
|
||||
return config;
|
||||
});
|
||||
|
|
201
rpc.js
201
rpc.js
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
});
|
||||
|
|
115
www/file/main.js
115
www/file/main.js
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue