Merge branch 'staging' into cba

pull/1/head
yflory 5 years ago
commit dd814713dd

@ -89,6 +89,14 @@ module.exports = {
*/ */
//httpSafePort: 3001, //httpSafePort: 3001,
/* CryptPad will launch a child process for every core available
* in order to perform CPU-intensive tasks in parallel.
* Some host environments may have a very large number of cores available
* or you may want to limit how much computing power CryptPad can take.
* If so, set 'maxWorkers' to a positive integer.
*/
// maxWorkers: 4,
/* ===================== /* =====================
* Admin * Admin
* ===================== */ * ===================== */

@ -2,7 +2,7 @@ html, body {
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
} }
#sbox-iframe, #sbox-share-iframe, #sbox-filePicker-iframe { #sbox-iframe, #sbox-secure-iframe {
position: fixed; position: fixed;
top:0; left:0; top:0; left:0;
bottom:0; right:0; bottom:0; right:0;

@ -54,6 +54,7 @@ server {
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-XSS-Protection "1; mode=block"; add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options nosniff; add_header X-Content-Type-Options nosniff;
add_header Access-Control-Allow-Origin "*";
# add_header X-Frame-Options "SAMEORIGIN"; # add_header X-Frame-Options "SAMEORIGIN";
# Insert the path to your CryptPad repository root here # Insert the path to your CryptPad repository root here

@ -1,4 +1,5 @@
/*jshint esversion: 6 */ /*jshint esversion: 6 */
/* globals process */
const nThen = require("nthen"); const nThen = require("nthen");
const getFolderSize = require("get-folder-size"); const getFolderSize = require("get-folder-size");
const Util = require("../common-util"); const Util = require("../common-util");
@ -50,6 +51,7 @@ var getCacheStats = function (env, server, cb) {
metaSize: metaSize, metaSize: metaSize,
channel: channelCount, channel: channelCount,
channelSize: channelSize, channelSize: channelSize,
memoryUsage: process.memoryUsage(),
}); });
}; };

@ -54,16 +54,8 @@ Channel.clearOwnedChannel = function (Env, safeKey, channelId, cb, Server) {
}); });
}; };
Channel.removeOwnedChannel = function (Env, safeKey, channelId, cb, Server) { var archiveOwnedChannel = function (Env, safeKey, channelId, cb, Server) {
if (typeof(channelId) !== 'string' || !Core.isValidId(channelId)) {
return cb('INVALID_ARGUMENTS');
}
var unsafeKey = Util.unescapeKeyCharacters(safeKey); var unsafeKey = Util.unescapeKeyCharacters(safeKey);
if (Env.blobStore.isFileId(channelId)) {
return void Env.removeOwnedBlob(channelId, safeKey, cb);
}
Metadata.getMetadata(Env, channelId, function (err, metadata) { Metadata.getMetadata(Env, channelId, function (err, metadata) {
if (err) { return void cb(err); } if (err) { return void cb(err); }
if (!Core.hasOwners(metadata)) { return void cb('E_NO_OWNERS'); } if (!Core.hasOwners(metadata)) { return void cb('E_NO_OWNERS'); }
@ -124,6 +116,24 @@ Channel.removeOwnedChannel = function (Env, safeKey, channelId, cb, Server) {
}); });
}; };
Channel.removeOwnedChannel = function (Env, safeKey, channelId, __cb, Server) {
var _cb = Util.once(Util.mkAsync(__cb));
if (typeof(channelId) !== 'string' || !Core.isValidId(channelId)) {
return _cb('INVALID_ARGUMENTS');
}
// archiving large channels or files can be expensive, so do it one at a time
// for any given user to ensure that nobody can use too much of the server's resources
Env.queueDeletes(safeKey, function (next) {
var cb = Util.both(_cb, next);
if (Env.blobStore.isFileId(channelId)) {
return void Env.removeOwnedBlob(channelId, safeKey, cb);
}
archiveOwnedChannel(Env, safeKey, channelId, cb, Server);
});
};
Channel.trimHistory = function (Env, safeKey, data, cb) { Channel.trimHistory = function (Env, safeKey, data, cb) {
if (!(data && typeof(data.channel) === 'string' && typeof(data.hash) === 'string' && data.hash.length === 64)) { if (!(data && typeof(data.channel) === 'string' && typeof(data.hash) === 'string' && data.hash.length === 64)) {
return void cb('INVALID_ARGS'); return void cb('INVALID_ARGS');

@ -49,16 +49,19 @@ var loadUserPins = function (Env, safeKey, cb) {
// only put this into the cache if it completes // only put this into the cache if it completes
session.channels = value; session.channels = value;
} }
session.channels = value;
done(value); done(value);
}); });
}); });
}; };
var truthyKeys = function (O) { var truthyKeys = function (O) {
return Object.keys(O).filter(function (k) { try {
return O[k]; return Object.keys(O).filter(function (k) {
}); return O[k];
});
} catch (err) {
return [];
}
}; };
var getChannelList = Pinning.getChannelList = function (Env, safeKey, _cb) { var getChannelList = Pinning.getChannelList = function (Env, safeKey, _cb) {

@ -31,13 +31,13 @@ module.exports.create = function (config, cb) {
// and more easily share state between historyKeeper and rpc // and more easily share state between historyKeeper and rpc
const Env = { const Env = {
Log: Log, Log: Log,
// tasks
// store // store
id: Crypto.randomBytes(8).toString('hex'), id: Crypto.randomBytes(8).toString('hex'),
metadata_cache: {}, metadata_cache: {},
channel_cache: {}, channel_cache: {},
queueStorage: WriteQueue(), queueStorage: WriteQueue(),
queueDeletes: WriteQueue(),
batchIndexReads: BatchRead("HK_GET_INDEX"), batchIndexReads: BatchRead("HK_GET_INDEX"),
batchMetadata: BatchRead('GET_METADATA'), batchMetadata: BatchRead('GET_METADATA'),
@ -98,7 +98,7 @@ module.exports.create = function (config, cb) {
paths.staging = keyOrDefaultString('blobStagingPath', './blobstage'); paths.staging = keyOrDefaultString('blobStagingPath', './blobstage');
paths.blob = keyOrDefaultString('blobPath', './blob'); paths.blob = keyOrDefaultString('blobPath', './blob');
Env.defaultStorageLimit = typeof(config.defaultStorageLimit) === 'number' && config.defaultStorageLimit > 0? Env.defaultStorageLimit = typeof(config.defaultStorageLimit) === 'number' && config.defaultStorageLimit >= 0?
config.defaultStorageLimit: config.defaultStorageLimit:
Core.DEFAULT_LIMIT; Core.DEFAULT_LIMIT;
@ -252,17 +252,14 @@ module.exports.create = function (config, cb) {
channelExpirationMs: config.channelExpirationMs, channelExpirationMs: config.channelExpirationMs,
verbose: config.verbose, verbose: config.verbose,
openFileLimit: config.openFileLimit, openFileLimit: config.openFileLimit,
maxWorkers: config.maxWorkers,
}, w(function (err) { }, w(function (err) {
if (err) { if (err) {
throw new Error(err); throw new Error(err);
} }
})); }));
}).nThen(function (w) { }).nThen(function (w) {
// create a task store (for scheduling tasks)
require("./storage/tasks").create(config, w(function (e, tasks) {
if (e) { throw e; }
Env.tasks = tasks;
}));
if (config.disableIntegratedTasks) { return; } if (config.disableIntegratedTasks) { return; }
config.intervals = config.intervals || {}; config.intervals = config.intervals || {};

@ -529,7 +529,7 @@ const handleFirstMessage = function (Env, channelName, metadata) {
if(metadata.expire && typeof(metadata.expire) === 'number') { if(metadata.expire && typeof(metadata.expire) === 'number') {
// the fun part... // the fun part...
// the user has said they want this pad to expire at some point // the user has said they want this pad to expire at some point
Env.tasks.write(metadata.expire, "EXPIRE", [ channelName ], function (err) { Env.writeTask(metadata.expire, "EXPIRE", [ channelName ], function (err) {
if (err) { if (err) {
// if there is an error, we don't want to crash the whole server... // if there is an error, we don't want to crash the whole server...
// just log it, and if there's a problem you'll be able to fix it // just log it, and if there's a problem you'll be able to fix it
@ -621,7 +621,10 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(msg)], readMore); Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(msg)], readMore);
}, (err) => { }, (err) => {
if (err && err.code !== 'ENOENT') { if (err && err.code !== 'ENOENT') {
if (err.message !== 'EINVAL') { Log.error("HK_GET_HISTORY", err); } if (err.message !== 'EINVAL') { Log.error("HK_GET_HISTORY", {
err: err && err.message,
stack: err && err.stack,
}); }
const parsedMsg = {error:err.message, channel: channelName, txid: txid}; const parsedMsg = {error:err.message, channel: channelName, txid: txid};
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg)]); Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg)]);
return; return;
@ -669,6 +672,7 @@ const handleGetHistoryRange = function (Env, Server, seq, userId, parsed) {
if (!Array.isArray(messages)) { messages = []; } if (!Array.isArray(messages)) { messages = []; }
// FIXME this reduction could be done in the worker instead of the main process
var toSend = []; var toSend = [];
if (typeof (desiredMessages) === "number") { if (typeof (desiredMessages) === "number") {
toSend = messages.slice(-desiredMessages); toSend = messages.slice(-desiredMessages);

@ -1,113 +0,0 @@
/* jshint esversion: 6 */
/* global process */
const Nacl = require('tweetnacl/nacl-fast');
const COMMANDS = {};
COMMANDS.INLINE = function (data, cb) {
var signedMsg;
try {
signedMsg = Nacl.util.decodeBase64(data.msg);
} catch (e) {
return void cb('E_BAD_MESSAGE');
}
var validateKey;
try {
validateKey = Nacl.util.decodeBase64(data.key);
} catch (e) {
return void cb("E_BADKEY");
}
// validate the message
const validated = Nacl.sign.open(signedMsg, validateKey);
if (!validated) {
return void cb("FAILED");
}
cb();
};
const checkDetachedSignature = function (signedMsg, signature, publicKey) {
if (!(signedMsg && publicKey)) { return false; }
var signedBuffer;
var pubBuffer;
var signatureBuffer;
try {
signedBuffer = Nacl.util.decodeUTF8(signedMsg);
} catch (e) {
throw new Error("INVALID_SIGNED_BUFFER");
}
try {
pubBuffer = Nacl.util.decodeBase64(publicKey);
} catch (e) {
throw new Error("INVALID_PUBLIC_KEY");
}
try {
signatureBuffer = Nacl.util.decodeBase64(signature);
} catch (e) {
throw new Error("INVALID_SIGNATURE");
}
if (pubBuffer.length !== 32) {
throw new Error("INVALID_PUBLIC_KEY_LENGTH");
}
if (signatureBuffer.length !== 64) {
throw new Error("INVALID_SIGNATURE_LENGTH");
}
if (Nacl.sign.detached.verify(signedBuffer, signatureBuffer, pubBuffer) !== true) {
throw new Error("FAILED");
}
};
COMMANDS.DETACHED = function (data, cb) {
try {
checkDetachedSignature(data.msg, data.sig, data.key);
} catch (err) {
return void cb(err && err.message);
}
cb();
};
COMMANDS.HASH_CHANNEL_LIST = function (data, cb) {
var channels = data.channels;
if (!Array.isArray(channels)) { return void cb('INVALID_CHANNEL_LIST'); }
var uniques = [];
channels.forEach(function (a) {
if (uniques.indexOf(a) === -1) { uniques.push(a); }
});
uniques.sort();
var hash = Nacl.util.encodeBase64(Nacl.hash(Nacl
.util.decodeUTF8(JSON.stringify(uniques))));
cb(void 0, hash);
};
process.on('message', function (data) {
if (!data || !data.txid) {
return void process.send({
error:'E_INVAL'
});
}
const cb = function (err, value) {
process.send({
txid: data.txid,
error: err,
value: value,
});
};
const command = COMMANDS[data.command];
if (typeof(command) !== 'function') {
return void cb("E_BAD_COMMAND");
}
command(data, cb);
});

@ -12,6 +12,7 @@ const Core = require("../commands/core");
const Saferphore = require("saferphore"); const Saferphore = require("saferphore");
const Logger = require("../log"); const Logger = require("../log");
const Tasks = require("../storage/tasks"); const Tasks = require("../storage/tasks");
const Nacl = require('tweetnacl/nacl-fast');
const Env = { const Env = {
Log: {}, Log: {},
@ -418,6 +419,10 @@ const runTasks = function (data, cb) {
Env.tasks.runAll(cb); Env.tasks.runAll(cb);
}; };
const writeTask = function (data, cb) {
Env.tasks.write(data.time, data.task_command, data.args, cb);
};
const COMMANDS = { const COMMANDS = {
COMPUTE_INDEX: computeIndex, COMPUTE_INDEX: computeIndex,
COMPUTE_METADATA: computeMetadata, COMPUTE_METADATA: computeMetadata,
@ -430,6 +435,92 @@ const COMMANDS = {
GET_HASH_OFFSET: getHashOffset, GET_HASH_OFFSET: getHashOffset,
REMOVE_OWNED_BLOB: removeOwnedBlob, REMOVE_OWNED_BLOB: removeOwnedBlob,
RUN_TASKS: runTasks, RUN_TASKS: runTasks,
WRITE_TASK: writeTask,
};
COMMANDS.INLINE = function (data, cb) {
var signedMsg;
try {
signedMsg = Nacl.util.decodeBase64(data.msg);
} catch (e) {
return void cb('E_BAD_MESSAGE');
}
var validateKey;
try {
validateKey = Nacl.util.decodeBase64(data.key);
} catch (e) {
return void cb("E_BADKEY");
}
// validate the message
const validated = Nacl.sign.open(signedMsg, validateKey);
if (!validated) {
return void cb("FAILED");
}
cb();
};
const checkDetachedSignature = function (signedMsg, signature, publicKey) {
if (!(signedMsg && publicKey)) { return false; }
var signedBuffer;
var pubBuffer;
var signatureBuffer;
try {
signedBuffer = Nacl.util.decodeUTF8(signedMsg);
} catch (e) {
throw new Error("INVALID_SIGNED_BUFFER");
}
try {
pubBuffer = Nacl.util.decodeBase64(publicKey);
} catch (e) {
throw new Error("INVALID_PUBLIC_KEY");
}
try {
signatureBuffer = Nacl.util.decodeBase64(signature);
} catch (e) {
throw new Error("INVALID_SIGNATURE");
}
if (pubBuffer.length !== 32) {
throw new Error("INVALID_PUBLIC_KEY_LENGTH");
}
if (signatureBuffer.length !== 64) {
throw new Error("INVALID_SIGNATURE_LENGTH");
}
if (Nacl.sign.detached.verify(signedBuffer, signatureBuffer, pubBuffer) !== true) {
throw new Error("FAILED");
}
};
COMMANDS.DETACHED = function (data, cb) {
try {
checkDetachedSignature(data.msg, data.sig, data.key);
} catch (err) {
return void cb(err && err.message);
}
cb();
};
COMMANDS.HASH_CHANNEL_LIST = function (data, cb) {
var channels = data.channels;
if (!Array.isArray(channels)) { return void cb('INVALID_CHANNEL_LIST'); }
var uniques = [];
channels.forEach(function (a) {
if (uniques.indexOf(a) === -1) { uniques.push(a); }
});
uniques.sort();
var hash = Nacl.util.encodeBase64(Nacl.hash(Nacl
.util.decodeUTF8(JSON.stringify(uniques))));
cb(void 0, hash);
}; };
process.on('message', function (data) { process.on('message', function (data) {

@ -3,103 +3,14 @@
const Util = require("../common-util"); const Util = require("../common-util");
const nThen = require('nthen'); const nThen = require('nthen');
const OS = require("os"); const OS = require("os");
const numCPUs = OS.cpus().length;
const { fork } = require('child_process'); const { fork } = require('child_process');
const Workers = module.exports; const Workers = module.exports;
const PID = process.pid; const PID = process.pid;
const CRYPTO_PATH = 'lib/workers/crypto-worker';
const DB_PATH = 'lib/workers/db-worker'; const DB_PATH = 'lib/workers/db-worker';
const MAX_JOBS = 16;
Workers.initializeValidationWorkers = function (Env) { Workers.initialize = function (Env, config, _cb) {
if (typeof(Env.validateMessage) !== 'undefined') {
return void console.error("validation workers are already initialized");
}
// Create our workers
const workers = [];
for (let i = 0; i < numCPUs; i++) {
workers.push(fork(CRYPTO_PATH));
}
const response = Util.response(function (errLabel, info) {
Env.Log.error('HK_VALIDATE_WORKER__' + errLabel, info);
});
var initWorker = function (worker) {
worker.on('message', function (res) {
if (!res || !res.txid) { return; }
response.handle(res.txid, [res.error, res.value]);
});
var substituteWorker = Util.once( function () {
Env.Log.info("SUBSTITUTE_VALIDATION_WORKER", '');
var idx = workers.indexOf(worker);
if (idx !== -1) {
workers.splice(idx, 1);
}
// Spawn a new one
var w = fork(CRYPTO_PATH);
workers.push(w);
initWorker(w);
});
// Spawn a new process in one ends
worker.on('exit', substituteWorker);
worker.on('close', substituteWorker);
worker.on('error', function (err) {
substituteWorker();
Env.Log.error('VALIDATION_WORKER_ERROR', {
error: err,
});
});
};
workers.forEach(initWorker);
var nextWorker = 0;
const send = function (msg, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
// let's be paranoid about asynchrony and only calling back once..
nextWorker = (nextWorker + 1) % workers.length;
if (workers.length === 0 || typeof(workers[nextWorker].send) !== 'function') {
return void cb("INVALID_WORKERS");
}
var txid = msg.txid = Util.uid();
// expect a response within 45s
response.expect(txid, cb, 60000);
// Send the request
workers[nextWorker].send(msg);
};
Env.validateMessage = function (signedMsg, key, cb) {
send({
msg: signedMsg,
key: key,
command: 'INLINE',
}, cb);
};
Env.checkSignature = function (signedMsg, signature, publicKey, cb) {
send({
command: 'DETACHED',
sig: signature,
msg: signedMsg,
key: publicKey,
}, cb);
};
Env.hashChannelList = function (channels, cb) {
send({
command: 'HASH_CHANNEL_LIST',
channels: channels,
}, cb);
};
};
Workers.initializeIndexWorkers = function (Env, config, _cb) {
var cb = Util.once(Util.mkAsync(_cb)); var cb = Util.once(Util.mkAsync(_cb));
const workers = []; const workers = [];
@ -124,16 +35,60 @@ Workers.initializeIndexWorkers = function (Env, config, _cb) {
return response.expected(id)? guid(): id; return response.expected(id)? guid(): id;
}; };
var workerIndex = 0; var workerOffset = -1;
var sendCommand = function (msg, _cb) { var queue = [];
var cb = Util.once(Util.mkAsync(_cb)); var getAvailableWorkerIndex = function () {
// If there is already a backlog of tasks you can avoid some work
// by going to the end of the line
if (queue.length) { return -1; }
var L = workers.length;
if (L === 0) {
Log.error('NO_WORKERS_AVAILABLE', {
queue: queue.length,
});
return -1;
}
workerIndex = (workerIndex + 1) % workers.length; // cycle through the workers once
if (!isWorker(workers[workerIndex])) { // start from a different offset each time
return void cb("NO_WORKERS"); // return -1 if none are available
workerOffset = (workerOffset + 1) % L;
var temp;
for (let i = 0; i < L; i++) {
temp = (workerOffset + i) % L;
/* I'd like for this condition to be more efficient
(`Object.keys` is sub-optimal) but I found some bugs in my initial
implementation stemming from a task counter variable going out-of-sync
with reality when a worker crashed and its tasks were re-assigned to
its substitute. I'm sure it can be done correctly and efficiently,
but this is a relatively easy way to make sure it's always up to date.
We'll see how it performs in practice before optimizing.
*/
if (workers[temp] && Object.keys(workers[temp]).length < MAX_JOBS) {
return temp;
}
} }
return -1;
};
var state = workers[workerIndex]; var sendCommand = function (msg, _cb) {
var index = getAvailableWorkerIndex();
var state = workers[index];
// if there is no worker available:
if (!isWorker(state)) {
// queue the message for when one becomes available
queue.push({
msg: msg,
cb: _cb,
});
return;
}
var cb = Util.once(Util.mkAsync(_cb));
const txid = guid(); const txid = guid();
msg.txid = txid; msg.txid = txid;
@ -141,14 +96,42 @@ Workers.initializeIndexWorkers = function (Env, config, _cb) {
// track which worker is doing which jobs // track which worker is doing which jobs
state.tasks[txid] = msg; state.tasks[txid] = msg;
response.expect(txid, function (err, value) {
// clean up when you get a response response.expect(txid, cb, 60000);
delete state[txid];
cb(err, value);
}, 60000);
state.worker.send(msg); state.worker.send(msg);
}; };
var handleResponse = function (state, res) {
if (!res) { return; }
// handle log messages before checking if it was addressed to your PID
// it might still be useful to know what happened inside an orphaned worker
if (res.log) {
return void handleLog(res.log, res.label, res.info);
}
// but don't bother handling things addressed to other processes
// since it's basically guaranteed not to work
if (res.pid !== PID) {
return void Log.error("WRONG_PID", res);
}
if (!res.txid) { return; }
response.handle(res.txid, [res.error, res.value]);
delete state.tasks[res.txid];
if (!queue.length) { return; }
var nextMsg = queue.shift();
/* `nextMsg` was at the top of the queue.
We know that a job just finished and all of this code
is synchronous, so calling `sendCommand` should take the worker
which was just freed up. This is somewhat fragile though, so
be careful if you want to modify this block. The risk is that
we take something that was at the top of the queue and push it
to the back because the following msg took its place. OR, in an
even worse scenario, we cycle through the queue but don't run anything.
*/
sendCommand(nextMsg.msg, nextMsg.cb);
};
const initWorker = function (worker, cb) { const initWorker = function (worker, cb) {
const txid = guid(); const txid = guid();
@ -170,19 +153,7 @@ Workers.initializeIndexWorkers = function (Env, config, _cb) {
}); });
worker.on('message', function (res) { worker.on('message', function (res) {
if (!res) { return; } handleResponse(state, res);
// handle log messages before checking if it was addressed to your PID
// it might still be useful to know what happened inside an orphaned worker
if (res.log) {
return void handleLog(res.log, res.label, res.info);
}
// but don't bother handling things addressed to other processes
// since it's basically guaranteed not to work
if (res.pid !== PID) {
return void Log.error("WRONG_PID", res);
}
response.handle(res.txid, [res.error, res.value]);
}); });
var substituteWorker = Util.once(function () { var substituteWorker = Util.once(function () {
@ -222,7 +193,32 @@ Workers.initializeIndexWorkers = function (Env, config, _cb) {
}; };
nThen(function (w) { nThen(function (w) {
OS.cpus().forEach(function () { const max = config.maxWorkers;
var limit;
if (typeof(max) !== 'undefined') {
// the admin provided a limit on the number of workers
if (typeof(max) === 'number' && !isNaN(max)) {
if (max < 1) {
Log.info("INSUFFICIENT_MAX_WORKERS", max);
limit = 1;
}
} else {
Log.error("INVALID_MAX_WORKERS", '[' + max + ']');
}
}
var logged;
OS.cpus().forEach(function (cpu, index) {
if (limit && index >= limit) {
if (!logged) {
logged = true;
Log.info('WORKER_LIMIT', "(Opting not to use available CPUs beyond " + index + ')');
}
return;
}
initWorker(fork(DB_PATH), w(function (err) { initWorker(fork(DB_PATH), w(function (err) {
if (!err) { return; } if (!err) { return; }
w.abort(); w.abort();
@ -327,11 +323,42 @@ Workers.initializeIndexWorkers = function (Env, config, _cb) {
}, cb); }, cb);
}; };
Env.writeTask = function (time, command, args, cb) {
sendCommand({
command: 'WRITE_TASK',
time: time,
task_command: command,
args: args,
}, cb);
};
// Synchronous crypto functions
Env.validateMessage = function (signedMsg, key, cb) {
sendCommand({
msg: signedMsg,
key: key,
command: 'INLINE',
}, cb);
};
Env.checkSignature = function (signedMsg, signature, publicKey, cb) {
sendCommand({
command: 'DETACHED',
sig: signature,
msg: signedMsg,
key: publicKey,
}, cb);
};
Env.hashChannelList = function (channels, cb) {
sendCommand({
command: 'HASH_CHANNEL_LIST',
channels: channels,
}, cb);
};
cb(void 0); cb(void 0);
}); });
}; };
Workers.initialize = function (Env, config, cb) {
Workers.initializeValidationWorkers(Env);
Workers.initializeIndexWorkers(Env, config, cb);
};

@ -281,6 +281,7 @@ define([
if (MEDIA_TAG_MODES.indexOf(mode) !== -1) { if (MEDIA_TAG_MODES.indexOf(mode) !== -1) {
// Embedding is endabled // Embedding is endabled
framework.setMediaTagEmbedder(function (mt) { framework.setMediaTagEmbedder(function (mt) {
editor.focus();
editor.replaceSelection($(mt)[0].outerHTML); editor.replaceSelection($(mt)[0].outerHTML);
}); });
} else { } else {

@ -196,6 +196,7 @@ define([
]); ]);
var $frame = $(frame); var $frame = $(frame);
frame.closeModal = function (cb) { frame.closeModal = function (cb) {
frame.closeModal = function () {}; // Prevent further calls
$frame.fadeOut(150, function () { $frame.fadeOut(150, function () {
$frame.detach(); $frame.detach();
if (typeof(cb) === "function") { cb(); } if (typeof(cb) === "function") { cb(); }
@ -464,16 +465,23 @@ define([
UI.createModal = function (cfg) { UI.createModal = function (cfg) {
var $body = cfg.$body || $('body'); var $body = cfg.$body || $('body');
var $blockContainer = $body.find('#'+cfg.id); var $blockContainer = cfg.id && $body.find('#'+cfg.id);
if (!$blockContainer.length) { if (!$blockContainer || !$blockContainer.length) {
$blockContainer = $(h('div.cp-modal-container#'+cfg.id, { var id = '';
if (cfg.id) { id = '#'+cfg.id; }
$blockContainer = $(h('div.cp-modal-container'+id, {
tabindex: 1 tabindex: 1
})); }));
} }
var deleted = false;
var hide = function () { var hide = function () {
if (cfg.onClose) { return void cfg.onClose(); } if (deleted) { return; }
$blockContainer.hide(); $blockContainer.hide();
if (cfg.onClosed) { cfg.onClosed(); } if (!cfg.id) {
deleted = true;
$blockContainer.remove();
}
if (cfg.onClose) { cfg.onClose(); }
}; };
$blockContainer.html('').appendTo($body); $blockContainer.html('').appendTo($body);
var $block = $(h('div.cp-modal')).appendTo($blockContainer); var $block = $(h('div.cp-modal')).appendTo($blockContainer);

@ -175,9 +175,9 @@ define([
var common = config.common; var common = config.common;
var sframeChan = common.getSframeChannel(); var sframeChan = common.getSframeChannel();
var title = config.title; var title = config.title;
var friends = config.friends; var friends = config.friends || {};
var teams = config.teams || {};
var myName = common.getMetadataMgr().getUserData().name; var myName = common.getMetadataMgr().getUserData().name;
if (!friends) { return; }
var order = []; var order = [];
var smallCurves = Object.keys(friends).map(function (c) { var smallCurves = Object.keys(friends).map(function (c) {
@ -206,40 +206,28 @@ define([
delete friends[curve]; delete friends[curve];
}); });
var friendsList = UIElements.getUserGrid(Messages.share_linkFriends, { var others = [];
common: common, if (Object.keys(friends).length) {
data: friends, var friendsList = UIElements.getUserGrid(Messages.share_linkFriends, {
noFilter: false, common: common,
large: true data: friends,
}, refreshButtons); noFilter: false,
var friendDiv = friendsList.div; large: true
$div.append(friendDiv); }, refreshButtons);
var others = friendsList.icons; var friendDiv = friendsList.div;
$div.append(friendDiv);
others = friendsList.icons;
}
var privateData = common.getMetadataMgr().getPrivateData(); if (Object.keys(teams).length) {
var teamsData = Util.tryParse(JSON.stringify(privateData.teams)) || {}; var teamsList = UIElements.getUserGrid(Messages.share_linkTeam, {
var teams = {}; common: common,
Object.keys(teamsData).forEach(function (id) { noFilter: true,
// config.teamId only exists when we're trying to share a pad from a team drive large: true,
// In this case, we don't want to share the pad with the current team data: teams
if (config.teamId && config.teamId === id) { return; } }, refreshButtons);
if (!teamsData[id].hasSecondaryKey) { return; } $div.append(teamsList.div);
var t = teamsData[id]; }
teams[t.edPublic] = {
notifications: true,
displayName: t.name,
edPublic: t.edPublic,
avatar: t.avatar,
id: id
};
});
var teamsList = UIElements.getUserGrid(Messages.share_linkTeam, {
common: common,
noFilter: true,
large: true,
data: teams
}, refreshButtons);
$div.append(teamsList.div);
var shareButton = { var shareButton = {
className: 'primary cp-share-with-friends', className: 'primary cp-share-with-friends',
@ -395,6 +383,26 @@ define([
} }
}; };
var getEditableTeams = function (common, config) {
var privateData = common.getMetadataMgr().getPrivateData();
var teamsData = Util.tryParse(JSON.stringify(privateData.teams)) || {};
var teams = {};
Object.keys(teamsData).forEach(function (id) {
// config.teamId only exists when we're trying to share a pad from a team drive
// In this case, we don't want to share the pad with the current team
if (config.teamId && config.teamId === id) { return; }
if (!teamsData[id].hasSecondaryKey) { return; }
var t = teamsData[id];
teams[t.edPublic] = {
notifications: true,
displayName: t.name,
edPublic: t.edPublic,
avatar: t.avatar,
id: id
};
});
return teams;
};
var makeBurnAfterReadingUrl = function (common, href, channel, cb) { var makeBurnAfterReadingUrl = function (common, href, channel, cb) {
var keyPair = Hash.generateSignPair(); var keyPair = Hash.generateSignPair();
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
@ -643,7 +651,10 @@ define([
// Share with contacts tab // Share with contacts tab
var hasFriends = Object.keys(config.friends || {}).length !== 0; var teams = getEditableTeams(common, config);
config.teams = teams;
var hasFriends = Object.keys(config.friends || {}).length ||
Object.keys(teams).length;
var onFriendShare = Util.mkEvent(); var onFriendShare = Util.mkEvent();
@ -926,7 +937,10 @@ define([
}); });
// share with contacts tab // share with contacts tab
var hasFriends = Object.keys(config.friends || {}).length !== 0; var teams = getEditableTeams(common, config);
config.teams = teams;
var hasFriends = Object.keys(config.friends || {}).length ||
Object.keys(teams).length;
var friendsObject = hasFriends ? createShareWithFriends(config, null, getLinkValue) : noContactsMessage(common); var friendsObject = hasFriends ? createShareWithFriends(config, null, getLinkValue) : noContactsMessage(common);
var friendsList = friendsObject.content; var friendsList = friendsObject.content;
@ -1592,11 +1606,7 @@ define([
.text(Messages.accessButton)) .text(Messages.accessButton))
.click(common.prepareFeedback(type)) .click(common.prepareFeedback(type))
.click(function () { .click(function () {
require(['/common/inner/access.js'], function (Access) { sframeChan.event('EV_ACCESS_OPEN');
Access.getAccessModal(common, {}, function (e) {
if (e) { console.error(e); }
});
});
}); });
break; break;
case 'properties': case 'properties':
@ -1611,11 +1621,7 @@ define([
if (!data) { if (!data) {
return void UI.alert(Messages.autostore_notAvailable); return void UI.alert(Messages.autostore_notAvailable);
} }
require(['/common/inner/properties.js'], function (Properties) { sframeChan.event('EV_PROPERTIES_OPEN');
Properties.getPropertiesModal(common, {}, function (e) {
if (e) { console.error(e); }
});
});
}); });
}); });
break; break;
@ -2628,17 +2634,13 @@ define([
}); });
}; };
UIElements.initFilePicker = function (common, cfg) { UIElements.openFilePicker = function (common, types, cb) {
var onSelect = cfg.onSelect || $.noop;
var sframeChan = common.getSframeChannel(); var sframeChan = common.getSframeChannel();
sframeChan.on("EV_FILE_PICKED", function (data) { sframeChan.query("Q_FILE_PICKER_OPEN", types, function (err, data) {
onSelect(data); if (err) { return; }
cb(data);
}); });
}; };
UIElements.openFilePicker = function (common, types) {
var sframeChan = common.getSframeChannel();
sframeChan.event("EV_FILE_PICKER_OPEN", types);
};
UIElements.openTemplatePicker = function (common, force) { UIElements.openTemplatePicker = function (common, force) {
var metadataMgr = common.getMetadataMgr(); var metadataMgr = common.getMetadataMgr();
@ -2661,29 +2663,25 @@ define([
return; return;
} }
delete pickerCfg.hidden; delete pickerCfg.hidden;
common.openFilePicker(pickerCfg);
var first = true; // We can only pick a template once (for a new document) var first = true; // We can only pick a template once (for a new document)
var fileDialogCfg = { common.openFilePicker(pickerCfg, function (data) {
onSelect: function (data) { if (data.type === type && first) {
if (data.type === type && first) { UI.addLoadingScreen({hideTips: true});
UI.addLoadingScreen({hideTips: true}); var chatChan = common.getPadChat();
var chatChan = common.getPadChat(); var cursorChan = common.getCursorChannel();
var cursorChan = common.getCursorChannel(); sframeChan.query('Q_TEMPLATE_USE', {
sframeChan.query('Q_TEMPLATE_USE', { href: data.href,
href: data.href, chat: chatChan,
chat: chatChan, cursor: cursorChan
cursor: cursorChan }, function () {
}, function () { first = false;
first = false; UI.removeLoadingScreen();
UI.removeLoadingScreen(); Feedback.send('TEMPLATE_USED');
Feedback.send('TEMPLATE_USED'); });
}); if (focus) { focus.focus(); }
if (focus) { focus.focus(); } return;
return;
}
} }
}; });
common.initFilePicker(fileDialogCfg);
}; };
sframeChan.query("Q_TEMPLATE_EXIST", type, function (err, data) { sframeChan.query("Q_TEMPLATE_EXIST", type, function (err, data) {

@ -23,8 +23,17 @@ define([
init: function () {} init: function () {}
}; };
var mermaidThemeCSS = ".node rect { fill: #DDD; stroke: #AAA; } " +
"rect.task, rect.task0, rect.task2 { stroke-width: 1 !important; rx: 0 !important; } " +
"g.grid g.tick line { opacity: 0.25; }" +
"g.today line { stroke: red; stroke-width: 1; stroke-dasharray: 3; opacity: 0.5; }";
require(['mermaid', 'css!/code/mermaid-new.css'], function (_Mermaid) { require(['mermaid', 'css!/code/mermaid-new.css'], function (_Mermaid) {
Mermaid = _Mermaid; Mermaid = _Mermaid;
Mermaid.initialize({
gantt: { axisFormat: '%m-%d', },
"themeCSS": mermaidThemeCSS,
});
}); });
var highlighter = function () { var highlighter = function () {
@ -351,6 +360,12 @@ define([
// retrieve the attached source code which it was drawn // retrieve the attached source code which it was drawn
var src = el.getAttribute('mermaid-source'); var src = el.getAttribute('mermaid-source');
/* The new source might have syntax errors that will prevent rendering.
It might be preferable to keep the existing state instead of removing it
if you don't have something better to display. Ideally we should display
the cause of the syntax error so that the user knows what to correct. */
//if (!Mermaid.parse(src)) { } // TODO
// check if that source exists in the set of charts which are about to be rendered // check if that source exists in the set of charts which are about to be rendered
if (mermaid_source.indexOf(src) === -1) { if (mermaid_source.indexOf(src) === -1) {
// if it's not, then you can remove it // if it's not, then you can remove it
@ -418,7 +433,7 @@ define([
throw new Error(patch); throw new Error(patch);
} else { } else {
DD.apply($content[0], patch); DD.apply($content[0], patch);
var $mts = $content.find('media-tag:not(:has(*))'); var $mts = $content.find('media-tag');
$mts.each(function (i, el) { $mts.each(function (i, el) {
var $mt = $(el).contextmenu(function (e) { var $mt = $(el).contextmenu(function (e) {
e.preventDefault(); e.preventDefault();
@ -426,6 +441,16 @@ define([
$(contextMenu.menu).find('li').show(); $(contextMenu.menu).find('li').show();
contextMenu.show(e); contextMenu.show(e);
}); });
if ($mt.children().length) {
$mt.off('dblclick preview');
$mt.on('preview', onPreview($mt));
if ($mt.find('img').length) {
$mt.on('dblclick', function () {
$mt.trigger('preview');
});
}
return;
}
MediaTag(el); MediaTag(el);
var observer = new MutationObserver(function(mutations) { var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) { mutations.forEach(function(mutation) {

@ -59,7 +59,7 @@ define([
}), opts.href); }), opts.href);
// If this is a file, don't try to look for metadata // If this is a file, don't try to look for metadata
if (opts.channel && opts.channel.length > 34) { return; } if (opts.channel && opts.channel.length > 32) { return; }
if (opts.channel) { data.channel = opts.channel; } if (opts.channel) { data.channel = opts.channel; }
Modal.loadMetadata(Env, data, waitFor); Modal.loadMetadata(Env, data, waitFor);
}).nThen(function () { }).nThen(function () {
@ -129,7 +129,10 @@ define([
tabs[i] = { tabs[i] = {
content: c && UI.dialog.customModal(node, { content: c && UI.dialog.customModal(node, {
buttons: obj.buttons || button, buttons: obj.buttons || button,
onClose: function () { blocked = false; } onClose: function () {
blocked = false;
if (typeof(opts.onClose) === "function") { opts.onClose(); }
}
}), }),
disabled: !c, disabled: !c,
title: obj.title, title: obj.title,

@ -132,6 +132,12 @@ define([
APP.onLocal(); APP.onLocal();
}; };
var isRegisteredUserOnline = function () {
var users = metadataMgr.getMetadata().users || {};
return Object.keys(users).some(function (id) {
return users[id] && users[id].curvePublic;
});
};
var isUserOnline = function (ooid) { var isUserOnline = function (ooid) {
// Remove ids for users that have left the channel // Remove ids for users that have left the channel
deleteOffline(); deleteOffline();
@ -348,16 +354,44 @@ define([
fixSheets(); fixSheets();
APP.FM.handleFile(blob, data); APP.FM.handleFile(blob, data);
}; };
Messages.oo_login = 'Log in...'; // XXX
var noLogin = false;
var makeCheckpoint = function (force) { var makeCheckpoint = function (force) {
if (!common.isLoggedIn()) { return; }
var locked = content.saveLock; var locked = content.saveLock;
var lastCp = getLastCp(); var lastCp = getLastCp();
var needCp = force || ooChannel.cpIndex % CHECKPOINT_INTERVAL === 0 || var needCp = force || ooChannel.cpIndex % CHECKPOINT_INTERVAL === 0 ||
(ooChannel.cpIndex - lastCp.index) > CHECKPOINT_INTERVAL; (ooChannel.cpIndex - (lastCp.index || 0)) > CHECKPOINT_INTERVAL;
if (!needCp) { return; } if (!needCp) { return; }
if (!locked || !isUserOnline(locked) || force) { if (!locked || !isUserOnline(locked) || force) {
if (!common.isLoggedIn() && !isRegisteredUserOnline() && !noLogin) {
var login = h('button.cp-corner-primary', Messages.login_login);
var register = h('button.cp-corner-primary', Messages.login_register);
var cancel = h('button.cp-corner-cancel', Messages.cancel);
var actions = h('div', [cancel, register, login]);
var modal = UI.cornerPopup(Messages.oo_login, actions, '', {alt: true});
$(register).click(function () {
common.setLoginRedirect(function () {
common.gotoURL('/register/');
});
modal.delete();
});
$(login).click(function () {
common.setLoginRedirect(function () {
common.gotoURL('/login/');
});
modal.delete();
});
$(cancel).click(function () {
modal.delete();
noLogin = true;
});
return;
}
if (!common.isLoggedIn()) { return; }
content.saveLock = myOOId; content.saveLock = myOOId;
APP.onLocal(); APP.onLocal();
APP.realtime.onSettle(function () { APP.realtime.onSettle(function () {
@ -907,8 +941,16 @@ define([
if (ifr) { ifr.remove(); } if (ifr) { ifr.remove(); }
}; };
common.initFilePicker({ APP.AddImage = function(cb1, cb2) {
onSelect: function (data) { APP.AddImageSuccessCallback = cb1;
APP.AddImageErrorCallback = cb2;
common.openFilePicker({
types: ['file'],
where: ['root'],
filter: {
fileType: ['image/']
}
}, function (data) {
if (data.type !== 'file') { if (data.type !== 'file') {
debug("Unexpected data type picked " + data.type); debug("Unexpected data type picked " + data.type);
return; return;
@ -929,19 +971,6 @@ define([
}); });
}); });
}); });
}
});
APP.AddImage = function(cb1, cb2) {
APP.AddImageSuccessCallback = cb1;
APP.AddImageErrorCallback = cb2;
common.openFilePicker({
types: ['file'],
where: ['root'],
filter: {
fileType: ['image/']
}
}); });
}; };
@ -1091,12 +1120,12 @@ define([
var x2tSaveAndConvertData = function(data, filename, extension, finalFilename) { var x2tSaveAndConvertData = function(data, filename, extension, finalFilename) {
// Perform the x2t conversion // Perform the x2t conversion
require(['/common/onlyoffice/x2t/x2t.js'], function() { require(['/common/onlyoffice/x2t/x2t.js'], function() { // XXX why does this fail without an access-control-allow-origin header?
var x2t = window.Module; var x2t = window.Module;
x2t.run(); x2t.run();
if (x2tInitialized) { if (x2tInitialized) {
debug("x2t runtime already initialized"); debug("x2t runtime already initialized");
x2tSaveAndConvertDataInternal(x2t, data, filename, extension, finalFilename); x2tSaveAndConvertDataInternal(x2t, data, filename, extension, finalFilename); // XXX shouldn't this return ?
} }
x2t.onRuntimeInitialized = function() { x2t.onRuntimeInitialized = function() {

@ -675,6 +675,8 @@ define([
sem.take(function (give) { sem.take(function (give) {
var otherOwners = false; var otherOwners = false;
nThen(function (_w) { nThen(function (_w) {
// Don't check server metadata for blobs
if (c.length !== 32) { return; }
Store.anonRpcMsg(null, { Store.anonRpcMsg(null, {
msg: 'GET_METADATA', msg: 'GET_METADATA',
data: c data: c
@ -1807,6 +1809,7 @@ define([
var cb = Util.once(Util.mkAsync(_cb)); var cb = Util.once(Util.mkAsync(_cb));
if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); } if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); }
if (data.channel.length !== 32) { return void cb({ error: 'EINVAL'}); }
store.anon_rpc.send('GET_METADATA', data.channel, function (err, obj) { store.anon_rpc.send('GET_METADATA', data.channel, function (err, obj) {
if (err) { return void cb({error: err}); } if (err) { return void cb({error: err}); }
var metadata = (obj && obj[0]) || {}; var metadata = (obj && obj[0]) || {};

@ -678,6 +678,8 @@ define([
sem.take(function (give) { sem.take(function (give) {
var otherOwners = false; var otherOwners = false;
nThen(function (_w) { nThen(function (_w) {
// Don't check server metadata for blobs
if (c.length !== 32) { return; }
ctx.Store.anonRpcMsg(null, { ctx.Store.anonRpcMsg(null, {
msg: 'GET_METADATA', msg: 'GET_METADATA',
data: c data: c

@ -503,8 +503,13 @@ define([
var createFilePicker = function () { var createFilePicker = function () {
if (!common.isLoggedIn()) { return; } if (!common.isLoggedIn()) { return; }
common.initFilePicker({ $embedButton = common.createButton('mediatag', true).click(function () {
onSelect: function (data) { var cfg = {
types: ['file'],
where: ['root']
};
if ($embedButton.data('filter')) { cfg.filter = $embedButton.data('filter'); }
common.openFilePicker(cfg, function (data) {
if (data.type !== 'file') { if (data.type !== 'file') {
console.log("Unexpected data type picked " + data.type); console.log("Unexpected data type picked " + data.type);
return; return;
@ -516,17 +521,7 @@ define([
var src = data.src = data.src.slice(0,1) === '/' ? origin + data.src : data.src; var src = data.src = data.src.slice(0,1) === '/' ? origin + data.src : data.src;
mediaTagEmbedder($('<media-tag src="' + src + mediaTagEmbedder($('<media-tag src="' + src +
'" data-crypto-key="cryptpad:' + data.key + '"></media-tag>'), data); '" data-crypto-key="cryptpad:' + data.key + '"></media-tag>'), data);
} });
});
$embedButton = common.createButton('mediatag', true).click(function () {
var cfg = {
types: ['file'],
where: ['root']
};
if ($embedButton.data('filter')) {
cfg.filter = $embedButton.data('filter');
}
common.openFilePicker(cfg);
}).appendTo(toolbar.$rightside).hide(); }).appendTo(toolbar.$rightside).hide();
}; };
var setMediaTagEmbedder = function (mte, filter) { var setMediaTagEmbedder = function (mte, filter) {

@ -18,8 +18,7 @@ define([
var Cryptget; var Cryptget;
var SFrameChannel; var SFrameChannel;
var sframeChan; var sframeChan;
var FilePicker; var SecureIframe;
var Share;
var Messaging; var Messaging;
var Notifier; var Notifier;
var Utils = { var Utils = {
@ -44,8 +43,7 @@ define([
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
'/common/cryptget.js', '/common/cryptget.js',
'/common/outer/worker-channel.js', '/common/outer/worker-channel.js',
'/filepicker/main.js', '/secureiframe/main.js',
'/share/main.js',
'/common/common-messaging.js', '/common/common-messaging.js',
'/common/common-notifier.js', '/common/common-notifier.js',
'/common/common-hash.js', '/common/common-hash.js',
@ -58,15 +56,14 @@ define([
'/common/test.js', '/common/test.js',
'/common/userObject.js', '/common/userObject.js',
], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel, ], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel,
_FilePicker, _Share, _Messaging, _Notifier, _Hash, _Util, _Realtime, _SecureIframe, _Messaging, _Notifier, _Hash, _Util, _Realtime,
_Constants, _Feedback, _LocalStore, _AppConfig, _Test, _UserObject) { _Constants, _Feedback, _LocalStore, _AppConfig, _Test, _UserObject) {
CpNfOuter = _CpNfOuter; CpNfOuter = _CpNfOuter;
Cryptpad = _Cryptpad; Cryptpad = _Cryptpad;
Crypto = Utils.Crypto = _Crypto; Crypto = Utils.Crypto = _Crypto;
Cryptget = _Cryptget; Cryptget = _Cryptget;
SFrameChannel = _SFrameChannel; SFrameChannel = _SFrameChannel;
FilePicker = _FilePicker; SecureIframe = _SecureIframe;
Share = _Share;
Messaging = _Messaging; Messaging = _Messaging;
Notifier = _Notifier; Notifier = _Notifier;
Utils.Hash = _Hash; Utils.Hash = _Hash;
@ -491,7 +488,7 @@ define([
// Put in the following function the RPC queries that should also work in filepicker // Put in the following function the RPC queries that should also work in filepicker
var addCommonRpc = function (sframeChan) { var addCommonRpc = function (sframeChan, safe) {
sframeChan.on('Q_ANON_RPC_MESSAGE', function (data, cb) { sframeChan.on('Q_ANON_RPC_MESSAGE', function (data, cb) {
Cryptpad.anonRpcMsg(data.msg, data.content, function (err, response) { Cryptpad.anonRpcMsg(data.msg, data.content, function (err, response) {
cb({error: err, response: response}); cb({error: err, response: response});
@ -598,6 +595,12 @@ define([
} }
if (data.href) { href = data.href; } if (data.href) { href = data.href; }
Cryptpad.getPadAttribute(data.key, function (e, data) { Cryptpad.getPadAttribute(data.key, function (e, data) {
if (!safe && data) {
// Remove unsafe data for the unsafe iframe
delete data.href;
delete data.roHref;
delete data.password;
}
cb({ cb({
error: e, error: e,
data: data data: data
@ -985,81 +988,63 @@ define([
onFileUpload(sframeChan, data, cb); onFileUpload(sframeChan, data, cb);
}); });
// File picker // Secure modal
var FP = {}; var SecureModal = {};
var initFilePicker = function (cfg) { // Create or display the iframe and modal
// cfg.hidden means pre-loading the filepicker while keeping it hidden. var initSecureModal = function (type, cfg, cb) {
cfg.modal = type;
SecureModal.cb = cb;
// cfg.hidden means pre-loading the iframe while keeping it hidden.
// if cfg.hidden is true and the iframe already exists, do nothing // if cfg.hidden is true and the iframe already exists, do nothing
if (!FP.$iframe) { if (!SecureModal.$iframe) {
var config = {}; var config = {};
config.onFilePicked = function (data) { config.onAction = function (data) {
sframeChan.event('EV_FILE_PICKED', data); if (typeof(SecureModal.cb) !== "function") { return; }
SecureModal.cb(data);
SecureModal.$iframe.hide();
}; };
config.onClose = function () { config.onClose = function () {
FP.$iframe.hide(); SecureModal.$iframe.hide();
};
config.onFileUpload = onFileUpload;
config.types = cfg;
config.addCommonRpc = addCommonRpc;
config.modules = {
Cryptpad: Cryptpad,
SFrameChannel: SFrameChannel,
Utils: Utils
}; };
FP.$iframe = $('<iframe>', {id: 'sbox-filePicker-iframe'}).appendTo($('body')); config.data = {
FP.picker = FilePicker.create(config); hashes: hashes,
} else if (!cfg.hidden) { password: password,
FP.$iframe.show(); isTemplate: isTemplate
FP.picker.refresh(cfg);
}
if (cfg.hidden) {
FP.$iframe.hide();
return;
}
FP.$iframe.focus();
};
sframeChan.on('EV_FILE_PICKER_OPEN', function (data) {
initFilePicker(data);
});
// Share modal
var ShareModal = {};
var initShareModal = function (cfg) {
cfg.hashes = hashes;
cfg.password = password;
cfg.isTemplate = isTemplate;
// cfg.hidden means pre-loading the filepicker while keeping it hidden.
// if cfg.hidden is true and the iframe already exists, do nothing
if (!ShareModal.$iframe) {
var config = {};
config.onShareAction = function (data) {
sframeChan.event('EV_SHARE_ACTION', data);
};
config.onClose = function () {
ShareModal.$iframe.hide();
}; };
config.data = cfg;
config.addCommonRpc = addCommonRpc; config.addCommonRpc = addCommonRpc;
config.modules = { config.modules = {
Cryptpad: Cryptpad, Cryptpad: Cryptpad,
SFrameChannel: SFrameChannel, SFrameChannel: SFrameChannel,
Utils: Utils Utils: Utils
}; };
ShareModal.$iframe = $('<iframe>', {id: 'sbox-share-iframe'}).appendTo($('body')); SecureModal.$iframe = $('<iframe>', {id: 'sbox-secure-iframe'}).appendTo($('body'));
ShareModal.modal = Share.create(config); SecureModal.modal = SecureIframe.create(config);
} else if (!cfg.hidden) { } else if (!cfg.hidden) {
ShareModal.modal.refresh(cfg, function () { SecureModal.modal.refresh(cfg, function () {
ShareModal.$iframe.show(); SecureModal.$iframe.show();
}); });
} }
if (cfg.hidden) { if (cfg.hidden) {
ShareModal.$iframe.hide(); SecureModal.$iframe.hide();
return; return;
} }
ShareModal.$iframe.focus(); SecureModal.$iframe.focus();
}; };
sframeChan.on('Q_FILE_PICKER_OPEN', function (data, cb) {
initSecureModal('filepicker', data || {}, cb);
});
sframeChan.on('EV_PROPERTIES_OPEN', function (data) {
initSecureModal('properties', data || {}, null);
});
sframeChan.on('EV_ACCESS_OPEN', function (data) {
initSecureModal('access', data || {}, null);
});
sframeChan.on('EV_SHARE_OPEN', function (data) { sframeChan.on('EV_SHARE_OPEN', function (data) {
initShareModal(data || {}); initSecureModal('share', data || {}, null);
}); });
sframeChan.on('Q_TEMPLATE_USE', function (data, cb) { sframeChan.on('Q_TEMPLATE_USE', function (data, cb) {
@ -1275,6 +1260,9 @@ define([
var _parsed = Utils.Hash.parsePadUrl(metadata.roHref); var _parsed = Utils.Hash.parsePadUrl(metadata.roHref);
_secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password); _secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password);
} }
if (_secret.channel.length !== 32) {
return void cb({error: 'EINVAL'});
}
var crypto = Crypto.createEncryptor(_secret.keys); var crypto = Crypto.createEncryptor(_secret.keys);
nThen(function (waitFor) { nThen(function (waitFor) {
// Try to get the owner's mailbox from the pad metadata first. // Try to get the owner's mailbox from the pad metadata first.
@ -1334,6 +1322,9 @@ define([
var _parsed = Utils.Hash.parsePadUrl(metadata.href || metadata.roHref); var _parsed = Utils.Hash.parsePadUrl(metadata.href || metadata.roHref);
_secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password); _secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password);
} }
if (_secret.channel.length !== 32) {
return void cb({error: 'EINVAL'});
}
var crypto = Crypto.createEncryptor(_secret.keys); var crypto = Crypto.createEncryptor(_secret.keys);
nThen(function (waitFor) { nThen(function (waitFor) {
// If we already have metadata, use it, otherwise, try to get it // If we already have metadata, use it, otherwise, try to get it

@ -89,7 +89,6 @@ define([
window.CryptPad_UIElements = UIElements; window.CryptPad_UIElements = UIElements;
window.CryptPad_common = funcs; window.CryptPad_common = funcs;
funcs.createUserAdminMenu = callWithCommon(UIElements.createUserAdminMenu); funcs.createUserAdminMenu = callWithCommon(UIElements.createUserAdminMenu);
funcs.initFilePicker = callWithCommon(UIElements.initFilePicker);
funcs.openFilePicker = callWithCommon(UIElements.openFilePicker); funcs.openFilePicker = callWithCommon(UIElements.openFilePicker);
funcs.openTemplatePicker = callWithCommon(UIElements.openTemplatePicker); funcs.openTemplatePicker = callWithCommon(UIElements.openTemplatePicker);
funcs.displayMediatagImage = callWithCommon(MT.displayMediatagImage); funcs.displayMediatagImage = callWithCommon(MT.displayMediatagImage);

@ -350,7 +350,7 @@
"fm_info_root": "Creeu aquí tantes carpetes imbrincades com vulgueu per ordenar els vostres fitxers.", "fm_info_root": "Creeu aquí tantes carpetes imbrincades com vulgueu per ordenar els vostres fitxers.",
"fm_info_unsorted": "Conté tots els fitxers que heu visitat i que encara no estan ordenats, a \"Documents\" o desplaçats a la \"Paperera\".", "fm_info_unsorted": "Conté tots els fitxers que heu visitat i que encara no estan ordenats, a \"Documents\" o desplaçats a la \"Paperera\".",
"fm_info_template": "Conté tots els documents desats com plantilles i que podeu reutilitzar quan vulgueu crear un nou document.", "fm_info_template": "Conté tots els documents desats com plantilles i que podeu reutilitzar quan vulgueu crear un nou document.",
"fm_info_recent": "Llista els documents modificats o oberts recentment.", "fm_info_recent": "Aquests documents s'han modificat o obert darrerament, per vós o per alguna persona col·laboradora.",
"fm_info_trash": "Buideu la paperera per alliberar espai al vostre CryptDrive.", "fm_info_trash": "Buideu la paperera per alliberar espai al vostre CryptDrive.",
"fm_info_allFiles": "Conté tots els fitxers de \"Documents\", \"Desordenats\" i \"Paperera\". No podeu desplaçar o suprimir fitxers des d'aquí.", "fm_info_allFiles": "Conté tots els fitxers de \"Documents\", \"Desordenats\" i \"Paperera\". No podeu desplaçar o suprimir fitxers des d'aquí.",
"fm_info_anonymous": "No heu iniciat la sessió, per tant, els vostres documents caducaran d'aquí a 3 mesos (<a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">saber-ne més</a>). Es desen al vostre navegador, per tant, si netegeu el vostre historial podríeu perdre'ls.<br><a href=\"/register/\">Registreu-vos</a> o <a href=\"/login/\">Inicieu la sessió</a> per mantenir-los accessibles.<br>", "fm_info_anonymous": "No heu iniciat la sessió, per tant, els vostres documents caducaran d'aquí a 3 mesos (<a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">saber-ne més</a>). Es desen al vostre navegador, per tant, si netegeu el vostre historial podríeu perdre'ls.<br><a href=\"/register/\">Registreu-vos</a> o <a href=\"/login/\">Inicieu la sessió</a> per mantenir-los accessibles.<br>",
@ -515,7 +515,7 @@
"settings_pinningError": "Alguna cosa no ha funcionat correctament", "settings_pinningError": "Alguna cosa no ha funcionat correctament",
"settings_usageAmount": "Els vostres documents fixats ocupen {0} MB", "settings_usageAmount": "Els vostres documents fixats ocupen {0} MB",
"settings_logoutEverywhereButton": "Tanca la sessió", "settings_logoutEverywhereButton": "Tanca la sessió",
"settings_logoutEverywhereTitle": "Tanca la sessió arreu", "settings_logoutEverywhereTitle": "Tanca les sessions remotes",
"settings_logoutEverywhere": "Tanca totes les altres sessions", "settings_logoutEverywhere": "Tanca totes les altres sessions",
"settings_logoutEverywhereConfirm": "De debò? Haureu de tornar a iniciar la vostra sessió a tots els dispositius.", "settings_logoutEverywhereConfirm": "De debò? Haureu de tornar a iniciar la vostra sessió a tots els dispositius.",
"settings_driveDuplicateTitle": "Documents propis duplicats", "settings_driveDuplicateTitle": "Documents propis duplicats",
@ -573,7 +573,7 @@
"upload_success": "El fitxer ({0}) ha estat carregat correctament i afegit al vostre CryptDrive.", "upload_success": "El fitxer ({0}) ha estat carregat correctament i afegit al vostre CryptDrive.",
"upload_notEnoughSpace": "No hi ha prou espai al CryptDrive per aquest fitxer.", "upload_notEnoughSpace": "No hi ha prou espai al CryptDrive per aquest fitxer.",
"upload_notEnoughSpaceBrief": "No hi ha prou espai", "upload_notEnoughSpaceBrief": "No hi ha prou espai",
"upload_tooLarge": "Aquest fitxer supera la mida màxima permesa.", "upload_tooLarge": "Aquest fitxer supera la mida màxima permesa pel vostre compte",
"upload_tooLargeBrief": "El fitxer és massa gran", "upload_tooLargeBrief": "El fitxer és massa gran",
"upload_choose": "Trieu un fitxer", "upload_choose": "Trieu un fitxer",
"upload_pending": "Pendent", "upload_pending": "Pendent",
@ -619,5 +619,20 @@
"download_resourceNotAvailable": "El recurs sol·licitat no estava disponible... Premeu Esc per continuar.", "download_resourceNotAvailable": "El recurs sol·licitat no estava disponible... Premeu Esc per continuar.",
"about_contributors": "Col·laboracions clau", "about_contributors": "Col·laboracions clau",
"about_core": "Desenvolupament principal", "about_core": "Desenvolupament principal",
"about_intro": "CryptPad s'ha creat dins l'Equip de Recerca de <a href=\"http://xwiki.com\">XWiki SAS</a>, una petita empresa de París, França i Iasi, Romania. Hi ha 3 membres de l'equip central treballant amb CryptPad més una quantitat de persones col·laboradores, dins i fora d'XWiki SAS." "about_intro": "CryptPad s'ha creat dins l'Equip de Recerca de <a href=\"http://xwiki.com\">XWiki SAS</a>, una petita empresa de París, França i Iasi, Romania. Hi ha 3 membres de l'equip central treballant amb CryptPad més una quantitat de persones col·laboradores, dins i fora d'XWiki SAS.",
"main_catch_phrase": "El Núvol Coneixement Zero",
"main_footerText": "Amb Cryptpad, podeu crear documents col·laboratius per prendre notes i posar en ordre idees de forma conjunta de forma ràpida.",
"footer_applications": "Aplicacions",
"footer_contact": "Contacte",
"footer_aboutUs": "Sobre nosaltres",
"about": "Sobre",
"contact": "Contacte",
"blog": "Bloc",
"topbar_whatIsCryptpad": "Què és CryptPad",
"whatis_collaboration": "Col·laboració fàcil i ràpida",
"whatis_title": "Què és CryptPad",
"terms": "Condicions d'ús",
"main_info": "<h2>Col·laboreu amb Confiança</h2>\nFeu créixer les vostres idees conjuntament amb documents compartits mentre la tecnologia <strong>Coneixement Zero</strong> assegura la vostra privacitat; <strong>fins i tot per nosaltres</strong>.",
"whatis_collaboration_p1": "Amb CryptPad, podeu crear de forma ràpida, documents col·laboratius per prendre notes i posar en ordre idees conjuntament. Quan us registreu i inicieu la vostra sessió, teniu la capacitat de carregar fitxers i un CryptDrive on podeu organitzar tots els vostres documents. Com a persona registrada disposeu de 50MB d'espai gratuït.",
"privacy": "Privacitat"
} }

@ -1,134 +0,0 @@
// Load #1, load as little as possible because we are in a race to get the loading screen up.
define([
'/bower_components/nthen/index.js',
'/api/config',
'jquery',
'/common/requireconfig.js',
], function (nThen, ApiConfig, $, RequireConfig) {
var requireConfig = RequireConfig();
var create = function (config) {
// Loaded in load #2
var sframeChan;
nThen(function (waitFor) {
$(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
$('#sbox-filePicker-iframe').attr('src',
ApiConfig.httpSafeOrigin + '/filepicker/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req)));
// This is a cheap trick to avoid loading sframe-channel in parallel with the
// loading screen setup.
var done = waitFor();
var onMsg = function (msg) {
var data = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) {
var Cryptpad = config.modules.Cryptpad;
var Utils = config.modules.Utils;
nThen(function (waitFor) {
// The inner iframe tries to get some data from us every ms (cache, store...).
// It will send a "READY" message and wait for our answer with the correct txid.
// First, we have to answer to this message, otherwise we're going to block
// sframe-boot.js. Then we can start the channel.
var msgEv = Utils.Util.mkEvent();
var iframe = $('#sbox-filePicker-iframe')[0].contentWindow;
var postMsg = function (data) {
iframe.postMessage(data, '*');
};
var w = waitFor();
var whenReady = function (msg) {
if (msg.source !== iframe) { return; }
var data = JSON.parse(msg.data);
if (!data.txid) { return; }
// Remove the listener once we've received the READY message
window.removeEventListener('message', whenReady);
// Answer with the requested data
postMsg(JSON.stringify({ txid: data.txid, language: Cryptpad.getLanguage(), localStore: window.localStore, cache: window.cpCache }));
// Then start the channel
window.addEventListener('message', function (msg) {
if (msg.source !== iframe) { return; }
msgEv.fire(msg);
});
config.modules.SFrameChannel.create(msgEv, postMsg, waitFor(function (sfc) {
sframeChan = sfc;
}));
w();
};
window.addEventListener('message', whenReady);
}).nThen(function () {
var updateMeta = function () {
//console.log('EV_METADATA_UPDATE');
var metaObj;
nThen(function (waitFor) {
Cryptpad.getMetadata(waitFor(function (err, n) {
if (err) { console.log(err); }
metaObj = n;
}));
}).nThen(function (/*waitFor*/) {
metaObj.doc = {};
var additionalPriv = {
fileHost: ApiConfig.fileHost,
loggedIn: Utils.LocalStore.isLoggedIn(),
origin: window.location.origin,
pathname: window.location.pathname,
feedbackAllowed: Utils.Feedback.state,
types: config.types
};
for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; }
sframeChan.event('EV_METADATA_UPDATE', metaObj);
});
};
Cryptpad.onMetadataChanged(updateMeta);
sframeChan.onReg('EV_METADATA_UPDATE', updateMeta);
config.addCommonRpc(sframeChan);
sframeChan.on('Q_GET_FILES_LIST', function (types, cb) {
Cryptpad.getSecureFilesList(types, function (err, data) {
cb({
error: err,
data: data
});
});
});
sframeChan.on('EV_FILE_PICKER_CLOSE', function () {
config.onClose();
});
sframeChan.on('EV_FILE_PICKED', function (data) {
config.onFilePicked(data);
});
sframeChan.on('Q_UPLOAD_FILE', function (data, cb) {
config.onFileUpload(sframeChan, data, cb);
});
});
});
var refresh = function (types) {
if (!sframeChan) { return; }
sframeChan.event('EV_FILE_PICKER_REFRESH', types);
};
return {
refresh: refresh
};
};
return {
create: create
};
});

@ -1206,22 +1206,18 @@ define([
updatePublishButton(); updatePublishButton();
if (common.isLoggedIn()) { if (common.isLoggedIn()) {
var fileDialogCfg = {
onSelect: function (data) {
if (data.type === 'file' && APP.editor) {
var mt = '<media-tag src="' + data.src + '" data-crypto-key="cryptpad:' + data.key + '"></media-tag>';
APP.editor.replaceSelection(mt);
return;
}
}
};
common.initFilePicker(fileDialogCfg);
APP.$mediaTagButton = common.createButton('mediatag', true).click(function () { APP.$mediaTagButton = common.createButton('mediatag', true).click(function () {
var pickerCfg = { var pickerCfg = {
types: ['file'], types: ['file'],
where: ['root'] where: ['root']
}; };
common.openFilePicker(pickerCfg); common.openFilePicker(pickerCfg, function (data) {
if (data.type === 'file' && APP.editor) {
var mt = '<media-tag src="' + data.src + '" data-crypto-key="cryptpad:' + data.key + '"></media-tag>';
APP.editor.replaceSelection(mt);
return;
}
});
}).appendTo($rightside); }).appendTo($rightside);
var $tags = common.createButton('hashtag', true); var $tags = common.createButton('hashtag', true);

@ -6,8 +6,11 @@
@import (reference) '../../customize/src/less2/include/tippy.less'; @import (reference) '../../customize/src/less2/include/tippy.less';
@import (reference) '../../customize/src/less2/include/checkmark.less'; @import (reference) '../../customize/src/less2/include/checkmark.less';
@import (reference) '../../customize/src/less2/include/password-input.less'; @import (reference) '../../customize/src/less2/include/password-input.less';
@import (reference) '../../customize/src/less2/include/modals-ui-elements.less';
@import (reference) '../../customize/src/less2/include/usergrid.less';
&.cp-app-filepicker { &.cp-app-secureiframe {
.modals-ui-elements_main();
.iconColors_main(); .iconColors_main();
.fileupload_main(); .fileupload_main();
.alertify_main(); .alertify_main();
@ -15,6 +18,7 @@
.checkmark_main(20px); .checkmark_main(20px);
.password_main(); .password_main();
.modal_main(); .modal_main();
.usergrid_main();
#cp-filepicker-dialog { #cp-filepicker-dialog {
display: none; display: none;
@ -26,6 +30,14 @@
overflow-y: auto; overflow-y: auto;
} }
.cp-loading-spinner-container {
height: 100px;
.cp-spinner {
border-color: @colortheme_logo-2;
border-top-color: transparent;
}
}
.cp-filepicker-content-element { .cp-filepicker-content-element {
width: 125px; width: 125px;
//min-width: 200px; //min-width: 200px;

@ -2,7 +2,7 @@
<html style="height: 100%; background: transparent;"> <html style="height: 100%; background: transparent;">
<head> <head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/> <meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/filepicker/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script> <script async data-bootload="/secureiframe/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style> <style>
.loading-hidden { display: none; } .loading-hidden { display: none; }
body #cp-loading { body #cp-loading {
@ -22,7 +22,7 @@
} }
</style> </style>
</head> </head>
<body class="cp-app-filepicker" style="background: transparent;"> <body class="cp-app-secureiframe" style="background: transparent;">
</body> </body>
</html> </html>

@ -7,12 +7,13 @@ define([
'/common/common-ui-elements.js', '/common/common-ui-elements.js',
'/common/common-util.js', '/common/common-util.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/hyperscript.js',
'json.sortify', 'json.sortify',
'/customize/messages.js', '/customize/messages.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/filepicker/app-filepicker.less', 'less!/secureiframe/app-secure.less',
], function ( ], function (
$, $,
Crypto, Crypto,
@ -22,6 +23,7 @@ define([
UIElements, UIElements,
Util, Util,
Hash, Hash,
h,
Sortify, Sortify,
Messages) Messages)
{ {
@ -29,23 +31,88 @@ define([
var andThen = function (common) { var andThen = function (common) {
var metadataMgr = common.getMetadataMgr(); var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
var $body = $('body');
var sframeChan = common.getSframeChannel(); var sframeChan = common.getSframeChannel();
var filters = metadataMgr.getPrivateData().types; var $body = $('body');
var hideIframe = function () {
sframeChan.event('EV_SECURE_IFRAME_CLOSE');
};
var displayed;
var create = {};
// Share modal
create['share'] = function (data) {
var priv = metadataMgr.getPrivateData();
var f = (data && data.file) ? UIElements.createFileShareModal
: UIElements.createShareModal;
var friends = common.getFriends();
var _modal;
var modal = f({
origin: priv.origin,
pathname: priv.pathname,
password: priv.password,
isTemplate: priv.isTemplate,
hashes: priv.hashes,
common: common,
title: data.title,
friends: friends,
onClose: function () {
if (_modal && _modal.close) { _modal.close(); }
hideIframe();
},
fileData: {
hash: priv.hashes.fileHash,
password: priv.password
}
});
$('button.cancel').click(); // Close any existing alertify
_modal = UI.openCustomModal(modal);
displayed = modal;
};
// Properties modal
create['properties'] = function () {
require(['/common/inner/properties.js'], function (Properties) {
Properties.getPropertiesModal(common, {
onClose: function () {
hideIframe();
}
}, function (e, modal) {
if (e) { console.error(e); }
displayed = modal;
});
});
};
var hideFileDialog = function () { // Access modal
sframeChan.event('EV_FILE_PICKER_CLOSE'); create['access'] = function () {
require(['/common/inner/access.js'], function (Access) {
Access.getAccessModal(common, {
onClose: function () {
hideIframe();
}
}, function (e, modal) {
if (e) { console.error(e); }
displayed = modal;
});
});
}; };
// File uploader
var onFilePicked = function (data) { var onFilePicked = function (data) {
var privateData = metadataMgr.getPrivateData();
var parsed = Hash.parsePadUrl(data.url); var parsed = Hash.parsePadUrl(data.url);
hideFileDialog(); if (displayed && displayed.hide) { displayed.hide(); }
hideIframe();
if (parsed.type === 'file') { if (parsed.type === 'file') {
var secret = Hash.getSecrets('file', parsed.hash, data.password); var secret = Hash.getSecrets('file', parsed.hash, data.password);
var fileHost = privateData.fileHost || privateData.origin; var fileHost = privateData.fileHost || privateData.origin;
var src = fileHost + Hash.getBlobPathFromHex(secret.channel); var src = fileHost + Hash.getBlobPathFromHex(secret.channel);
var key = Hash.encodeBase64(secret.keys.cryptKey); var key = Hash.encodeBase64(secret.keys.cryptKey);
sframeChan.event("EV_FILE_PICKED", { sframeChan.event("EV_SECURE_ACTION", {
type: parsed.type, type: parsed.type,
src: src, src: src,
name: data.name, name: data.name,
@ -53,14 +120,12 @@ define([
}); });
return; return;
} }
sframeChan.event("EV_FILE_PICKED", { sframeChan.event("EV_SECURE_ACTION", {
type: parsed.type, type: parsed.type,
href: data.url, href: data.url,
name: data.name name: data.name
}); });
}; };
// File uploader
var fmConfig = { var fmConfig = {
body: $('body'), body: $('body'),
noHandlers: true, noHandlers: true,
@ -69,40 +134,40 @@ define([
} }
}; };
APP.FM = common.createFileManager(fmConfig); APP.FM = common.createFileManager(fmConfig);
create['filepicker'] = function (_filters) {
var updateContainer = function () {};
// Create file picker var filters = _filters;
var onSelect = function (url, name, password) {
onFilePicked({url: url, name: name, password: password});
};
var data = {
FM: APP.FM
};
var updateContainer;
var createFileDialog = function () {
var types = filters.types || []; var types = filters.types || [];
var data = {
FM: APP.FM
};
// Create modal // Create modal
var modal = UI.createModal({ var modal = UI.createModal({
id: 'cp-filepicker-dialog',
$body: $body, $body: $body,
onClose: hideFileDialog onClose: function () {
hideIframe();
}
}); });
displayed = modal;
modal.show(); modal.show();
var $blockContainer = modal.$modal;
// Set the fixed content // Set the fixed content
var $block = $blockContainer.find('.cp-modal'); modal.$modal.attr('id', 'cp-filepicker-dialog');
var $block = modal.$modal.find('.cp-modal');
// Description // Description
var text = Messages.filePicker_description; var text = Messages.filePicker_description;
if (types && types.length === 1 && types[0] !== 'file') { if (types && types.length === 1 && types[0] !== 'file') {
// Should be Templates
text = Messages.selectTemplate; text = Messages.selectTemplate;
} }
var $description = $('<p>').text(text); $block.append(h('p', text));
$block.append($description);
var $filter = $('<p>', {'class': 'cp-modal-form'}).appendTo($block); // Add filter input
var $filter = $(h('p.cp-modal-form')).appendTo($block);
var to; var to;
$('<input>', { var $input = $('<input>', {
type: 'text', type: 'text',
'class': 'cp-filepicker-filter', 'class': 'cp-filepicker-filter',
'placeholder': Messages.filePicker_filter 'placeholder': Messages.filePicker_filter
@ -111,7 +176,7 @@ define([
to = window.setTimeout(updateContainer, 300); to = window.setTimeout(updateContainer, 300);
}); });
//If file, display the upload button // If file, display the upload button
if (types.indexOf('file') !== -1 && common.isLoggedIn()) { if (types.indexOf('file') !== -1 && common.isLoggedIn()) {
var f = (filters && filters.filter) || {}; var f = (filters && filters.filter) || {};
delete data.accept; delete data.accept;
@ -126,15 +191,16 @@ define([
$filter.append(common.createButton('upload', false, data)); $filter.append(common.createButton('upload', false, data));
} }
var $container = $('<span>', {'class': 'cp-filepicker-content'}).appendTo($block); var $container = $(h('span.cp-filepicker-content', [
h('div.cp-loading-spinner-container', h('span.cp-spinner'))
])).appendTo($block);
// Update the files list when needed // Update the files list when needed
updateContainer = function () { updateContainer = function () {
$container.html('');
var $input = $filter.find('.cp-filepicker-filter');
var filter = $input.val().trim(); var filter = $input.val().trim();
var todo = function (err, list) { var todo = function (err, list) {
if (err) { return void console.error(err); } if (err) { return void console.error(err); }
$container.html('');
Object.keys(list).forEach(function (id) { Object.keys(list).forEach(function (id) {
var data = list[id]; var data = list[id];
var name = data.filename || data.title || '?'; var name = data.filename || data.title || '?';
@ -149,8 +215,8 @@ define([
$('<span>', {'class': 'cp-filepicker-content-element-name'}).text(name) $('<span>', {'class': 'cp-filepicker-content-element-name'}).text(name)
.appendTo($span); .appendTo($span);
$span.click(function () { $span.click(function () {
if (typeof onSelect === "function") { if (typeof onFilePicked === "function") {
onSelect(data.href, name, data.password); onFilePicked({url: data.href, name: name, password: data.password});
} }
}); });
@ -163,21 +229,23 @@ define([
}; };
updateContainer(); updateContainer();
}; };
sframeChan.on('EV_FILE_PICKER_REFRESH', function (newFilters) {
if (Sortify(filters) !== Sortify(newFilters)) { sframeChan.on('EV_REFRESH', function (data) {
$body.html(''); if (!data) { return; }
filters = newFilters; var type = data.modal;
return void createFileDialog(); if (!create[type]) { return; }
} if (displayed && displayed.close) { displayed.close(); }
updateContainer(); else if (displayed && displayed.hide) { displayed.hide(); }
displayed = undefined;
create[type](data);
}); });
createFileDialog();
UI.removeLoadingScreen(); UI.removeLoadingScreen();
}; };
var main = function () { var main = function () {
var common; var common;
var _andThen = Util.once(andThen);
nThen(function (waitFor) { nThen(function (waitFor) {
$(waitFor(function () { $(waitFor(function () {
@ -187,11 +255,11 @@ define([
}).nThen(function (/*waitFor*/) { }).nThen(function (/*waitFor*/) {
var metadataMgr = common.getMetadataMgr(); var metadataMgr = common.getMetadataMgr();
if (metadataMgr.getMetadataLazy() !== 'uninitialized') { if (metadataMgr.getMetadataLazy() !== 'uninitialized') {
andThen(common); _andThen(common);
return; return;
} }
metadataMgr.onChange(function () { metadataMgr.onChange(function () {
andThen(common); _andThen(common);
}); });
}); });
}; };

@ -22,8 +22,8 @@ define([
}; };
window.rc = requireConfig; window.rc = requireConfig;
window.apiconf = ApiConfig; window.apiconf = ApiConfig;
$('#sbox-share-iframe').attr('src', $('#sbox-secure-iframe').attr('src',
ApiConfig.httpSafeOrigin + '/share/inner.html?' + requireConfig.urlArgs + ApiConfig.httpSafeOrigin + '/secureiframe/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req))); '#' + encodeURIComponent(JSON.stringify(req)));
// This is a cheap trick to avoid loading sframe-channel in parallel with the // This is a cheap trick to avoid loading sframe-channel in parallel with the
@ -48,7 +48,7 @@ define([
// First, we have to answer to this message, otherwise we're going to block // First, we have to answer to this message, otherwise we're going to block
// sframe-boot.js. Then we can start the channel. // sframe-boot.js. Then we can start the channel.
var msgEv = Utils.Util.mkEvent(); var msgEv = Utils.Util.mkEvent();
var iframe = $('#sbox-share-iframe')[0].contentWindow; var iframe = $('#sbox-secure-iframe')[0].contentWindow;
var postMsg = function (data) { var postMsg = function (data) {
iframe.postMessage(data, '*'); iframe.postMessage(data, '*');
}; };
@ -106,7 +106,11 @@ define([
Cryptpad.onMetadataChanged(updateMeta); Cryptpad.onMetadataChanged(updateMeta);
sframeChan.onReg('EV_METADATA_UPDATE', updateMeta); sframeChan.onReg('EV_METADATA_UPDATE', updateMeta);
config.addCommonRpc(sframeChan); config.addCommonRpc(sframeChan, true);
Cryptpad.padRpc.onMetadataEvent.reg(function (data) {
sframeChan.event('EV_RT_METADATA', data);
});
sframeChan.on('EV_CACHE_PUT', function (x) { sframeChan.on('EV_CACHE_PUT', function (x) {
Object.keys(x).forEach(function (k) { Object.keys(x).forEach(function (k) {
@ -132,11 +136,24 @@ define([
}); });
}); });
sframeChan.on('EV_SHARE_CLOSE', function () { sframeChan.on('EV_SECURE_ACTION', function (data) {
config.onClose(); config.onAction(data);
});
sframeChan.on('Q_UPLOAD_FILE', function (data, cb) {
config.onFileUpload(sframeChan, data, cb);
}); });
sframeChan.on('EV_SHARE_ACTION', function (data) {
config.onShareAction(data); sframeChan.on('Q_GET_FILES_LIST', function (types, cb) {
Cryptpad.getSecureFilesList(types, function (err, data) {
cb({
error: err,
data: data
});
});
});
sframeChan.on('EV_SECURE_IFRAME_CLOSE', function () {
config.onClose();
}); });
sframeChan.onReady(function () { sframeChan.onReady(function () {
@ -155,7 +172,7 @@ define([
}; };
return; return;
} }
sframeChan.event('EV_SHARE_REFRESH', data); sframeChan.event('EV_REFRESH', data);
cb(); cb();
}; };
return { return {

@ -1,17 +0,0 @@
@import (reference) '../../customize/src/less2/include/tippy.less';
@import (reference) '../../customize/src/less2/include/modal.less';
@import (reference) '../../customize/src/less2/include/alertify.less';
@import (reference) '../../customize/src/less2/include/checkmark.less';
@import (reference) '../../customize/src/less2/include/password-input.less';
@import (reference) '../../customize/src/less2/include/usergrid.less';
@import (reference) '../../customize/src/less2/include/modals-ui-elements.less';
&.cp-app-share {
.tippy_main();
.alertify_main();
.modals-ui-elements_main();
.checkmark_main(20px);
.password_main();
.modal_main();
.usergrid_main();
}

@ -1,30 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>CryptPad</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
</style>
</head>
<body>
<iframe id="sbox-iframe">

@ -1,29 +0,0 @@
<!DOCTYPE html>
<html style="height: 100%; background: transparent;">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/share/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
body #cp-loading {
display: none;
position: absolute;
top: 15vh;
bottom: 15vh;
left: 10vw;
right: 10vw;
z-index: 200000;
overflow: hidden;
}
body #cp-loading .cp-loading-container {
margin-top: 35vh;
}
body #cp-loading .cp-loading-cryptofist {
display: none;
}
</style>
</head>
<body class="cp-app-share" style="background: transparent;">
</body>
</html>

@ -1,87 +0,0 @@
define([
'jquery',
'/bower_components/nthen/index.js',
'/common/sframe-common.js',
'/common/common-ui-elements.js',
'/common/common-interface.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/share/app-share.less',
], function (
$,
nThen,
SFCommon,
UIElements,
UI)
{
var APP = window.APP = {};
var init = false;
var andThen = function (common) {
if (init) { return; }
init = true;
var metadataMgr = common.getMetadataMgr();
var sframeChan = common.getSframeChannel();
var hideShareDialog = function () {
sframeChan.event('EV_SHARE_CLOSE');
};
var createShareDialog = function (data) {
var priv = metadataMgr.getPrivateData();
var hashes = priv.hashes;
var origin = priv.origin;
var pathname = priv.pathname;
var f = (data && data.file) ? UIElements.createFileShareModal
: UIElements.createShareModal;
var friends = common.getFriends();
var modal = f({
origin: origin,
pathname: pathname,
password: priv.password,
isTemplate: priv.isTemplate,
hashes: hashes,
common: common,
title: data.title,
friends: friends,
onClose: function () {
hideShareDialog();
},
fileData: {
hash: hashes.fileHash,
password: priv.password
}
});
$('button.cancel').click(); // Close any existing alertify
UI.openCustomModal(modal);
};
sframeChan.on('EV_SHARE_REFRESH', function (data) {
createShareDialog(data);
});
};
var main = function () {
var common;
nThen(function (waitFor) {
$(waitFor(function () {
UI.removeLoadingScreen();
}));
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
}).nThen(function (/*waitFor*/) {
var metadataMgr = common.getMetadataMgr();
if (metadataMgr.getMetadataLazy() !== 'uninitialized') {
andThen(common);
return;
}
metadataMgr.onChange(function () {
andThen(common);
});
});
};
main();
});
Loading…
Cancel
Save