Remove duplicated files

This commit is contained in:
yflory 2025-04-17 13:34:32 +02:00
parent b5706da75c
commit c2a3fbd60c
70 changed files with 246 additions and 10805 deletions

View File

@ -7,7 +7,7 @@ define([
'chainpad-listmap',
'/components/chainpad-crypto/crypto.js',
'/common/common-util.js',
'/common/outer/network-config.js',
'/common/network-config.js',
'/common/common-login.js',
'/common/common-credential.js',
'/components/chainpad/chainpad.dist.js',

View File

@ -288,5 +288,5 @@ server {
}
# Finally, serve anything the above exceptions don't govern.
try_files /customize/www/$uri /customize/www/$uri/index.html /www/$uri /www/$uri/index.html /customize/$uri;
try_files /customize/www/$uri /customize/www/$uri/index.html /www/$uri /www/$uri/index.html /src/$uri /customize/$uri;
}

View File

@ -2,5 +2,5 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
module.exports = require("../www/common/common-hash");
module.exports = require("../src/common/common-hash");

View File

@ -2,4 +2,4 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
module.exports = require("../www/common/common-util");
module.exports = require("../src/common/common-util");

View File

@ -339,6 +339,8 @@ Object.keys(plugins || {}).forEach(name => {
app.use(Express.static(Path.resolve('./customize/www')));
app.use(Express.static(Path.resolve('./www')));
app.use("/common", Express.static('./src/common'));
var mainPages = Env.mainPages || Default.mainPages();
var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$');
app.get(mainPagePattern, Express.static('./customize'));

View File

@ -2,4 +2,4 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
module.exports = require("../www/common/common-signing-keys");
module.exports = require("../src/common/common-signing-keys");

View File

@ -1,54 +0,0 @@
// SPDX-FileCopyrightText: 2025 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// Move file from "src" to "www"
// This script can be used to copy src files into their old location in order to
// merge code more easily.
// Set REVERSE to true to copy from "www" to "src" once the changes have been merged.
const REVERSE = false;
const Fs = require('node:fs');
const map = {
'./src/worker/components/roster.js': './www/common/outer/roster.js',
'./src/worker/components/sharedfolder.js': './www/common/outer/sharedfolder.js',
'./src/common/cache-store.js': './www/common/outer/cache-store.js',
'./src/common/common-constants.js': './www/common/common-constants.js',
'./src/common/common-credential.js': './www/common/common-credential.js',
'./src/common/common-feedback.js': './www/common/common-feedback.js',
'./src/common/common-hash.js': './www/common/common-hash.js',
'./src/common/common-messaging.js': './www/common/common-messaging.js',
'./src/common/common-realtime.js': './www/common/common-realtime.js',
'./src/common/common-signing-keys.js': './www/common/common-signing-keys.js',
'./src/common/common-util.js': './www/common/common-util.js',
'./src/common/cryptget.js': './www/common/cryptget.js',
'./src/common/http-command.js': './www/common/outer/http-command.js',
'./src/common/login-block.js': './www/common/outer/login-block.js',
'./src/common/network-config.js': './www/common/outer/network-config.js',
'./src/common/notify.js': './www/common/notify.js',
'./src/common/onlyoffice/current-version.js': './www/common/onlyoffice/current-version.js',
'./src/common/pad-types.js': './www/common/pad-types.js',
'./src/common/pinpad.js': './www/common/pinpad.js',
'./src/common/proxy-manager.js': './www/common/proxy-manager.js',
'./src/common/recurrence.js': './www/calendar/recurrence.js',
'./src/common/rpc.js': './www/common/rpc.js',
'./src/common/user-object.js': './www/common/user-object.js',
'./src/common/user-object-setter.js': './www/common/user-object-setter.js',
'./src/common/worker-channel.js': './www/common/outer/worker-channel.js'
};
Object.keys(map).forEach(newPath => {
let oldPath = map[newPath];
if (!Fs.existsSync(newPath)) {
throw new Error("File path mismatch: " + newPath);
}
if (!Fs.existsSync(oldPath)) {
throw new Error("File path mismatch: " + oldPath);
}
const from = REVERSE ? oldPath : newPath;
const to = REVERSE ? newPath : oldPath;
Fs.cpSync(from, to);
});

View File

@ -225,8 +225,8 @@ if (typeof(module) !== 'undefined' && module.exports) {
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/outer/network-config.js',
'/common/outer/cache-store.js',
'/common/network-config.js',
'/common/cache-store.js',
'/common/pinpad.js',
'/components/nthen/index.js',
'/components/chainpad/chainpad.dist.js',

View File

@ -1,245 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => {
var Block = {};
Block.setCustomize = data => {
ApiConfig = data.ApiConfig;
ServerCommand.setCustomize(data);
};
Block.join = Util.uint8ArrayJoin;
// publickey <base64 string>
// signature <base64 string>
// block <base64 string>
// [b64_public, b64_sig, b64_block [version, nonce, content]]
Block.seed = function () {
return Nacl.hash(Util.decodeUTF8('pewpewpew'));
};
// should be deterministic from a seed...
Block.genkeys = function (seed) {
if (!(seed instanceof Uint8Array)) {
throw new Error('INVALID_SEED_FORMAT');
}
if (!seed || typeof(seed.length) !== 'number' || seed.length < 64) {
throw new Error('INVALID_SEED_LENGTH');
}
var signSeed = seed.subarray(0, Nacl.sign.seedLength);
var symmetric = seed.subarray(Nacl.sign.seedLength,
Nacl.sign.seedLength + Nacl.secretbox.keyLength);
return {
sign: Nacl.sign.keyPair.fromSeed(signSeed), // 32 bytes
symmetric: symmetric, // 32 bytes ...
};
};
Block.keysToRPCFormat = function (keys) {
try {
var sign = keys.sign;
return {
edPrivate: Util.encodeBase64(sign.secretKey),
edPublic: Util.encodeBase64(sign.publicKey),
};
} catch (err) {
console.error(err);
return;
}
};
// (UTF8 content, keys object) => Uint8Array block
Block.encrypt = function (version, content, keys) {
var u8 = Util.decodeUTF8(content);
var nonce = Nacl.randomBytes(Nacl.secretbox.nonceLength);
return Block.join([
[0],
nonce,
Nacl.secretbox(u8, nonce, keys.symmetric)
]);
};
// (uint8Array block) => payload object
Block.decrypt = function (u8_content, keys) {
// version is currently ignored since there is only one
var nonce = u8_content.subarray(1, 1 + Nacl.secretbox.nonceLength);
var box = u8_content.subarray(1 + Nacl.secretbox.nonceLength);
var plaintext = Nacl.secretbox.open(box, nonce, keys.symmetric);
try {
return JSON.parse(Util.encodeUTF8(plaintext));
} catch (e) {
console.error(e);
return;
}
};
// (Uint8Array block) => signature
Block.sign = function (ciphertext, keys) {
return Nacl.sign.detached(Nacl.hash(ciphertext), keys.sign.secretKey);
};
Block.serialize = function (content, keys) {
// encrypt the content
var ciphertext = Block.encrypt(0, content, keys);
// generate a detached signature
var sig = Block.sign(ciphertext, keys);
// serialize {publickey, sig, ciphertext}
return {
publicKey: Util.encodeBase64(keys.sign.publicKey),
signature: Util.encodeBase64(sig),
ciphertext: Util.encodeBase64(ciphertext),
};
};
Block.proveAncestor = function (O /* oldBlockKeys, N, newBlockKeys */) {
var u8_pub = Util.find(O, ['sign', 'publicKey']);
var u8_secret = Util.find(O, ['sign', 'secretKey']);
try {
// sign your old publicKey with your old privateKey
var u8_sig = Nacl.sign.detached(u8_pub, u8_secret);
// return an array with the sig and the pubkey
return JSON.stringify([u8_pub, u8_sig].map(Util.encodeBase64));
} catch (err) {
return void console.error(err);
}
};
var urlSafeB64 = function (u8) {
return Util.encodeBase64(u8).replace(/\//g, '-');
};
Block.getBlockUrl = function (keys) {
var publicKey = urlSafeB64(keys.sign.publicKey);
// 'block/' here is hardcoded because it's hardcoded on the server
// if we want to make CryptPad work in server subfolders, we'll need
// to update this path derivation
return (ApiConfig.fileHost || ApiConfig.httpUnsafeOrigin || window.location.origin)
+ '/block/' + publicKey.slice(0, 2) + '/' + publicKey;
};
Block.getBlockHash = function (keys) {
var absolute = Block.getBlockUrl(keys);
var symmetric = urlSafeB64(keys.symmetric);
return absolute + '#' + symmetric;
};
var decodeSafeB64 = function (b64) {
try {
return Util.decodeBase64(b64.replace(/\-/g, '/'));
} catch (e) {
console.error(e);
return;
}
};
Block.parseBlockHash = function (hash) {
if (typeof(hash) !== 'string') { return; }
var parts = hash.split('#');
if (parts.length !== 2) { return; }
try {
return {
href: parts[0],
keys: {
symmetric: decodeSafeB64(parts[1]),
}
};
} catch (e) {
console.error(e);
return;
}
};
Block.checkRights = function (data, _cb) {
const cb = Util.mkAsync(_cb);
const { blockKeys, auth } = data;
var command = 'MFA_CHECK';
if (auth && auth.type) { command = `${auth.type.toUpperCase()}_` + command; }
ServerCommand(blockKeys.sign, {
command: command,
auth: auth && auth.data
}, cb);
};
Block.writeLoginBlock = function (data, cb) {
const { content, blockKeys, oldBlockKeys, auth, pw, session, token, userData } = data;
var command = 'WRITE_BLOCK';
if (auth && auth.type) { command = `${auth.type.toUpperCase()}_` + command; }
var block = Block.serialize(JSON.stringify(content), blockKeys);
block.auth = auth && auth.data;
block.hasPassword = pw;
block.registrationProof = oldBlockKeys && Block.proveAncestor(oldBlockKeys);
if (token) { block.inviteToken = token; }
if (userData) { block.userData = userData; }
ServerCommand(blockKeys.sign, {
command: command,
content: block,
session: session // sso session
}, cb);
};
Block.removeLoginBlock = function (data, cb) {
const { reason, blockKeys, auth, edPublic } = data;
var command = 'REMOVE_BLOCK';
if (auth && auth.type) { command = `${auth.type.toUpperCase()}_` + command; }
ServerCommand(blockKeys.sign, {
command: command,
auth: auth && auth.data,
edPublic: edPublic,
reason: reason
}, cb);
};
Block.updateSSOBlock = function (data, cb) {
const { blockKeys, oldBlockKeys } = data;
var oldProof = oldBlockKeys && Block.proveAncestor(oldBlockKeys);
ServerCommand(blockKeys.sign, {
command: 'SSO_UPDATE_BLOCK',
ancestorProof: oldProof
}, cb);
};
return Block;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('./common-util'),
undefined,
require('./http-command'),
require('tweetnacl/nacl-fast')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/api/config',
'/common/outer/http-command.js',
'/components/tweetnacl/nacl-fast.min.js',
], (Util, ApiConfig, ServerCommand) => {
return factory(Util, ApiConfig, ServerCommand, window.nacl);
});
} else {
// unsupported initialization
}
})();

View File

@ -110,7 +110,7 @@ const factory = (nThen, Util, ApiConfig = {}, Nacl) => {
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('nthen'),
require('./common-util'),
require('../common-util'),
undefined,
require('tweetnacl/nacl-fast')
);

View File

@ -224,7 +224,7 @@ const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => {
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('./common-util'),
require('../common-util'),
undefined,
require('./http-command'),
require('tweetnacl/nacl-fast')

View File

@ -4,7 +4,7 @@
(() => {
const factory = (UserObject, Util, Hash,
SF, Messages = {}, Feedback, nThen) => {
Messages = {}, Feedback, nThen, SF = {}) => {
let setCustomize = data => {
Messages = data.Messages;
@ -1843,20 +1843,20 @@ if (typeof(module) !== 'undefined' && module.exports) {
require('./user-object'),
require('./common-util'),
require('./common-hash'),
require('../worker/components/sharedfolder'),
undefined,
require('./common-feedback'),
require('nthen')
require('nthen'),
require('../worker/components/sharedfolder'),
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/user-object.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/outer/sharedfolder.js',
'/customize/messages.js',
'/common/common-feedback.js',
'/components/nthen/index.js',
// sharedfolder.js not needed outside of worker
], factory);
} else {
// unsupported initialization

View File

@ -2,14 +2,13 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
const factory = (Sortify, UserObject, ProxyManager,
Migrate, Hash, Util, Constants, Feedback,
Realtime, Messaging, Pinpad, Rpc, Merge, Cache,
SF, AccountTS, DriveTS, Cursor,
Support, Integration, OnlyOffice,
Mailbox, Profile, Team, Messenger, History,
Calendar, Block, NetConfig, AppConfig = {},
Calendar, Block, NetConfig,
Crypto, ChainPad, CpNetflux, Listmap,
Netflux, nThen) => {
@ -18,6 +17,9 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
const window = globalThis;
globalThis.nacl = globalThis.nacl || Crypto.Nacl;
let ApiConfig = {};
let AppConfig = {};
const Saferphore = Util.Saferphore;
var onReadyEvt = Util.mkEvent(true);
var onCacheReadyEvt = Util.mkEvent(true);
@ -3452,49 +3454,40 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
};
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
undefined,
require('json.sortify'),
require('../common/user-object'),
require('../common/proxy-manager'),
require('./components/migrate-user-object'),
require('../common/common-hash'),
require('../common/common-util'),
require('../common/common-constants'),
require('../common/common-feedback'),
require('../common/common-realtime'),
require('../common/common-messaging'),
require('../common/pinpad'),
require('../common/rpc'),
require('./components/merge-drive'),
require('../common/cache-store'),
require('./components/sharedfolder'),
require('./components/account'), // .ts
require('./components/drive'), // .ts
require('./modules/cursor'),
require('./modules/support'),
require('./modules/integration'),
require('./modules/onlyoffice'),
require('./modules/mailbox'),
require('./modules/profile'),
require('./modules/team'),
require('./modules/messenger'),
require('./modules/history'),
require('./modules/calendar'),
require('../common/login-block'),
require('../common/network-config'),
undefined,
require('chainpad-crypto'),
require('chainpad'),
require('chainpad-netflux'),
require('chainpad-listmap'),
require('netflux-websocket'),
require('nthen')
);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('json.sortify'),
require('../common/user-object'),
require('../common/proxy-manager'),
require('./components/migrate-user-object'),
require('../common/common-hash'),
require('../common/common-util'),
require('../common/common-constants'),
require('../common/common-feedback'),
require('../common/common-realtime'),
require('../common/common-messaging'),
require('../common/pinpad'),
require('../common/rpc'),
require('./components/merge-drive'),
require('../common/cache-store'),
require('./components/sharedfolder'),
require('./components/account'), // .ts
require('./components/drive'), // .ts
require('./modules/cursor'),
require('./modules/support'),
require('./modules/integration'),
require('./modules/onlyoffice'),
require('./modules/mailbox'),
require('./modules/profile'),
require('./modules/team'),
require('./modules/messenger'),
require('./modules/history'),
require('./modules/calendar'),
require('../common/outer/login-block'),
require('../common/network-config'),
require('chainpad-crypto'),
require('chainpad'),
require('chainpad-netflux'),
require('chainpad-listmap'),
require('netflux-websocket'),
require('nthen')
);

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(function () {
var factory = function (Util, Cred, Nacl, Crypto) {
var Invite = {};
@ -102,21 +101,9 @@ var factory = function (Util, Cred, Nacl, Crypto) {
return Invite;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require("../../common/common-util"),
require("../../common/common-credential"),
require("tweetnacl/nacl-fast"),
require("chainpad-crypto")
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/common/common-credential.js',
'/components/chainpad-crypto/crypto.js',
'/components/tweetnacl/nacl-fast.min.js',
], function (Util, Cred, Crypto) {
return factory(Util, Cred, window.nacl, Crypto);
});
}
}());
module.exports = factory(
require("../../common/common-util"),
require("../../common/common-credential"),
require("tweetnacl/nacl-fast"),
require("chainpad-crypto")
);

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Messaging, Hash, Util, Crypto, Block) => {
// Random timeout between 10 and 30 times your sync time (lag + chainpad sync)
@ -983,24 +982,10 @@ const factory = (Messaging, Hash, Util, Crypto, Block) => {
};
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('../../common/common-messaging'),
require('../../common/common-hash'),
require('../../common/common-util'),
require('chainpad-crypto'),
require('../../common/login-block')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-messaging.js',
'/common/common-hash.js',
'/common/common-util.js',
'/components/chainpad-crypto/crypto.js',
'/common/outer/login-block.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/common-messaging'),
require('../../common/common-hash'),
require('../../common/common-util'),
require('chainpad-crypto'),
require('../../common/outer/login-block')
);

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Crypt, FO, Hash, Realtime) => {
var exp = {};
@ -91,22 +90,9 @@ const factory = (Crypt, FO, Hash, Realtime) => {
return exp;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('../../common/cryptget'),
require('../../common/user-object'),
require('../../common/common-hash'),
require('../../common/common-realtime')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/cryptget.js',
'/common/user-object.js',
'/common/common-hash.js',
'/common/common-realtime.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/cryptget'),
require('../../common/user-object'),
require('../../common/common-hash'),
require('../../common/common-realtime')
);

View File

@ -2,11 +2,11 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Feedback, Hash, Util,
Messaging, Crypt, Mailbox, Messages = {},
Messaging, Crypt, Mailbox,
Realtime, nThen, Crypto) => {
let Messages = {};
const setCustomize = data => {
Messages = data.Messages;
};
@ -507,35 +507,14 @@ const factory = (Feedback, Hash, Util,
return migrate;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('../../common/common-feedback'),
require('../../common/common-hash'),
require('../../common/common-util'),
require('../../common/common-messaging'),
require('../../common/cryptget'),
require('../modules/mailbox'),
undefined,
require('../../common/common-realtime'),
require('nthen'),
require('chainpad-crypto')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-feedback.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-messaging.js',
'/common/cryptget.js',
'/common/outer/mailbox.js',
'/customize/messages.js',
'/common/common-realtime.js',
'/components/nthen/index.js',
'/components/chainpad-crypto/crypto.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/common-feedback'),
require('../../common/common-hash'),
require('../../common/common-util'),
require('../../common/common-messaging'),
require('../../common/cryptget'),
require('../modules/mailbox'),
require('../../common/common-realtime'),
require('nthen'),
require('chainpad-crypto')
);

View File

@ -2,8 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(function () {
var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto, Feedback) {
var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
var Roster = {};
// this constant is somewhat arbitrary.
@ -618,7 +617,6 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto, Feedback)
// deleted while you are open
// emit an event
var onChannelError = function (info) {
if (Feedback) { Feedback.send('ROSTER_CHANNEL_ERROR='+(info && info.type)); }
if (info && info.type === "EUNKNOWN") {
// chainpad-netflux should recover by itself
return;
@ -926,39 +924,11 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto, Feedback)
return Roster;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require("../../common/common-util"),
require("../../common/common-hash"),
require('chainpad-netflux'),
require('json.sortify'),
require("nthen"),
require("chainpad-crypto"),
null // no feedback here
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
require.config({ paths: { 'json.sortify': '/components/json.sortify/dist/JSON.sortify' } });
define([
'/common/common-util.js',
'/common/common-hash.js',
'chainpad-netflux',
'json.sortify',
'/components/nthen/index.js',
'/components/chainpad-crypto/crypto.js',
'/common/common-feedback.js',
//'/components/tweetnacl/nacl-fast.min.js',
], function (Util, Hash, CPNF, Sortify, nThen, Crypto, Feedback) {
return factory.apply(null, [
Util,
Hash,
CPNF,
Sortify,
nThen,
Crypto,
Feedback
]);
});
} else {
// I'm not gonna bother supporting any other kind of instanciation
}
}());
module.exports = factory(
require("../../common/common-util"),
require("../../common/common-hash"),
require('chainpad-netflux'),
require('json.sortify'),
require("nthen"),
require("chainpad-crypto")
);

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Hash, Util, UserObject, Cache,
nThen, Crypto, Listmap, ChainPad) => {
var SF = {};
@ -387,31 +386,14 @@ const factory = (Hash, Util, UserObject, Cache,
return SF;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('../../common/common-hash'),
require('../../common/common-util'),
require('../../common/user-object'),
require('../../common/cache-store'),
require('nthen'),
require('chainpad-crypto'),
require('chainpad-listmap'),
require('chainpad')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-hash.js',
'/common/common-util.js',
'/common/user-object.js',
'/common/outer/cache-store.js',
module.exports = factory(
require('../../common/common-hash'),
require('../../common/common-util'),
require('../../common/user-object'),
require('../../common/cache-store'),
require('nthen'),
require('chainpad-crypto'),
require('chainpad-listmap'),
require('chainpad')
);
'/components/nthen/index.js',
'/components/chainpad-crypto/crypto.js',
'chainpad-listmap',
'/components/chainpad/chainpad.dist.js',
], factory);
} else {
// unsupported initialization
}
})();

View File

@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (SRpc, Channel, Util) => {
const Interface = {};
let store;
@ -122,19 +121,8 @@ const factory = (SRpc, Channel, Util) => {
return Interface;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('./store-rpc'),
require('../../common/worker-channel'),
require('../../common/common-util')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/outer/store-rpc.js',
'/common/outer/worker-channel.js',
'/common/common-util.js'
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('./store-rpc'),
require('../../common/events-channel'),
require('../../common/common-util')
);

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = AStore => {
var create = function (config) {
var Store = AStore.create(config);
@ -118,16 +117,6 @@ const factory = AStore => {
return { create };
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('../async-store')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/outer/async-store.js'
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../async-store')
);

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Util, Hash, Constants, Realtime, Cache, Rec,
nThen, Listmap, FP, Crypto, ChainPad) => {
var Calendar = {};
@ -1208,38 +1207,16 @@ const factory = (Util, Hash, Constants, Realtime, Cache, Rec,
return Calendar;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-constants'),
require('../../common/common-realtime'),
require('../../common/cache-store'),
require('../../common/recurrence'),
require('nthen'),
require('chainpad-listmap'),
require('../../../www/lib/datepicker/flatpickr'),
require('chainpad-crypto'),
require('chainpad'),
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-constants.js',
'/common/common-realtime.js',
'/common/outer/cache-store.js',
'/calendar/recurrence.js',
'/components/nthen/index.js',
'chainpad-listmap',
'/lib/datepicker/flatpickr.js',
'/components/chainpad-crypto/crypto.js',
'/components/chainpad/chainpad.dist.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-constants'),
require('../../common/common-realtime'),
require('../../common/cache-store'),
require('../../common/recurrence'),
require('nthen'),
require('chainpad-listmap'),
require('../../../www/lib/datepicker/flatpickr'),
require('chainpad-crypto'),
require('chainpad'),
);

View File

@ -2,10 +2,10 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Util, Constants, Messages = {},
AppConfig = {}, Crypto) => {
var Cursor = {};
const factory = (Util, Constants, Crypto) => {
const Cursor = {};
let Messages = {};
let AppConfig = {};
Cursor.setCustomize = data => {
Messages = data.Messages;
@ -286,25 +286,8 @@ const factory = (Util, Constants, Messages = {},
return Cursor;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-constants'),
undefined,
undefined,
require('chainpad-crypto')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/common/common-constants.js',
'/customize/messages.js',
'/customize/application_config.js',
'/components/chainpad-crypto/crypto.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-constants'),
require('chainpad-crypto')
);

View File

@ -2,10 +2,9 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Util, Hash, UserObject, nThen) => {
var History = {};
var commands = {};
const History = {};
const commands = {};
var getAccountChannels = function (ctx) {
var channels = [];
@ -257,22 +256,9 @@ const factory = (Util, Hash, UserObject, nThen) => {
return History;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/user-object'),
require('nthen')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/common/common-hash.js',
'/common/user-object.js',
'/components/nthen/index.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/user-object'),
require('nthen')
);

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Crypto) => {
var Integration = {};
@ -203,17 +202,6 @@ const factory = (Crypto) => {
return Integration;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('chainpad-crypto')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/components/chainpad-crypto/crypto.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('chainpad-crypto')
);

View File

@ -2,10 +2,10 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (BCast = {}, Util, Hash,
const factory = (Util, Hash,
Realtime, Messaging, Notify, Handlers, CpNetflux, Crypto) => {
var Mailbox = {};
const Mailbox = {};
let BCast = {};
Mailbox.setCustomize = data => {
BCast = data.Broadcast;
@ -673,32 +673,13 @@ proxy.mailboxes = {
return Mailbox;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
undefined,
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-realtime'),
require('../../common/common-messaging'),
require('../../common/notify'),
require('../components/mailbox-handlers'),
require('chainpad-netflux'),
require('chainpad-crypto')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/api/broadcast',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/common-messaging.js',
'/common/notify.js',
'/common/outer/mailbox-handlers.js',
'chainpad-netflux',
'/components/chainpad-crypto/crypto.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-realtime'),
require('../../common/common-messaging'),
require('../../common/notify'),
require('../components/mailbox-handlers'),
require('chainpad-netflux'),
require('chainpad-crypto')
);

View File

@ -2,12 +2,12 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Crypto, Hash, Util, Realtime, Messaging,
Constants, Messages = {}, PadTypes, nThen) => {
Constants, PadTypes, nThen) => {
var Curve = Crypto.Curve;
var Msg = {};
const Msg = {};
let Messages = {};
Msg.setCustomize = data => {
Messages = data.Messages;
@ -1119,34 +1119,13 @@ const factory = (Crypto, Hash, Util, Realtime, Messaging,
return Msg;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('chainpad-crypto'),
require('../../common/common-hash'),
require('../../common/common-util'),
require('../../common/common-realtime'),
require('../../common/common-messaging'),
require('../../common/common-constants'),
undefined,
require('../../common/pad-types'),
require('nthen')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/components/chainpad-crypto/crypto.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-realtime.js',
'/common/common-messaging.js',
'/common/common-constants.js',
'/customize/messages.js',
'/common/pad-types.js',
'/components/nthen/index.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('chainpad-crypto'),
require('../../common/common-hash'),
require('../../common/common-util'),
require('../../common/common-realtime'),
require('../../common/common-messaging'),
require('../../common/common-constants'),
require('../../common/pad-types'),
require('nthen')
);

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = () => {
var OO = {};
@ -350,12 +349,4 @@ const factory = () => {
return OO;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory();
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory();

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Util, Hash, Constants, Realtime,
Listmap, Crypto, ChainPad) => {
var Profile = {};
@ -156,29 +155,12 @@ const factory = (Util, Hash, Constants, Realtime,
return Profile;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-constants'),
require('../../common/common-realtime'),
require('chainpad-listmap'),
require('chainpad-crypto'),
require('chainpad')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-constants.js',
'/common/common-realtime.js',
'chainpad-listmap',
'/components/chainpad-crypto/crypto.js',
'/components/chainpad/chainpad.dist.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-constants'),
require('../../common/common-realtime'),
require('chainpad-listmap'),
require('chainpad-crypto'),
require('chainpad')
);

View File

@ -2,10 +2,10 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (ApiConfig = {}, Util, Hash, Realtime, Pinpad, Crypt,
const factory = (Util, Hash, Realtime, Pinpad, Crypt,
nThen, Crypto, Listmap, ChainPad, CpNetflux) => {
var Support = {};
const Support = {};
let ApiConfig = {};
Support.setCustomize = data => {
ApiConfig = data.ApiConfig;
@ -1420,37 +1420,15 @@ const factory = (ApiConfig = {}, Util, Hash, Realtime, Pinpad, Crypt,
return Support;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
undefined,
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-realtime'),
require('../../common/pinpad'),
require('../../common/cryptget'),
require('nthen'),
require('chainpad-crypto'),
require('chainpad-listmap'),
require('chainpad'),
require('chainpad-netflux')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/api/config',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/pinpad.js',
'/common/cryptget.js',
'/components/nthen/index.js',
'/components/chainpad-crypto/crypto.js',
'chainpad-listmap',
'/components/chainpad/chainpad.dist.js',
'chainpad-netflux'
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-realtime'),
require('../../common/pinpad'),
require('../../common/cryptget'),
require('nthen'),
require('chainpad-crypto'),
require('chainpad-listmap'),
require('chainpad'),
require('chainpad-netflux')
);

View File

@ -2,12 +2,11 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Util, Hash, Constants, Realtime, ProxyManager,
UserObject, SF, Roster, Messaging, Feedback,
Invite, Crypt, Cache, Pinpad, Listmap, Crypto,
CpNetflux, ChainPad, nThen, Nacl) => {
var Team = {};
const Team = {};
Nacl = Nacl || (typeof(window) !== "undefined" && window.nacl);
var onStoreReady = Util.mkEvent(true);
@ -2241,59 +2240,27 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
return Team;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-constants'),
require('../../common/common-realtime'),
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-constants'),
require('../../common/common-realtime'),
require('../../common/proxy-manager'),
require('../../common/user-object'),
require('../components/sharedfolder'),
require('../components/roster'),
require('../../common/common-messaging'),
require('../../common/common-feedback'),
require('../components/invitation'),
require('../../common/cryptget'),
require('../../common/cache-store'),
require('../../common/pinpad'),
require('../../common/proxy-manager'),
require('../../common/user-object'),
require('../components/sharedfolder'),
require('../components/roster'),
require('../../common/common-messaging'),
require('../../common/common-feedback'),
require('../components/invitation'),
require('../../common/cryptget'),
require('../../common/cache-store'),
require('../../common/pinpad'),
require('chainpad-listmap'),
require('chainpad-crypto'),
require('chainpad-netflux'),
require('chainpad'),
require('nthen'),
require('tweetnacl/nacl-fast'),
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-constants.js',
'/common/common-realtime.js',
'/common/proxy-manager.js',
'/common/user-object.js',
'/common/outer/sharedfolder.js',
'/common/outer/roster.js',
'/common/common-messaging.js',
'/common/common-feedback.js',
'/common/outer/invitation.js',
'/common/cryptget.js',
'/common/outer/cache-store.js',
'/common/pinpad.js',
'chainpad-listmap',
'/components/chainpad-crypto/crypto.js',
'chainpad-netflux',
'/components/chainpad/chainpad.dist.js',
'/components/nthen/index.js',
'/components/tweetnacl/nacl-fast.min.js',
], factory);
} else {
// unsupported initialization
}
})();
require('chainpad-listmap'),
require('chainpad-crypto'),
require('chainpad-netflux'),
require('chainpad'),
require('nthen'),
require('tweetnacl/nacl-fast'),
);

View File

@ -24,7 +24,7 @@ import * as UOSetter from '../common/user-object-setter.js';
import * as Pinpad from '../common/pinpad.js';
import * as PadTypes from '../common/pad-types.js';
import * as NetworkConfig from '../common/network-config.js';
import * as LoginBlock from '../common/login-block.js';
import * as LoginBlock from '../common/outer/login-block.js';
// Core
import * as Store from './async-store.js';

View File

@ -7,7 +7,7 @@
define([
'/customize/pages.js',
'/common/common-util.js',
'/calendar/recurrence.js',
'/common/recurrence.js',
'/lib/ical.min.js'
], function (Pages, Util, Rec) {

View File

@ -21,7 +21,7 @@ define([
'/customize/application_config.js',
'/lib/calendar/tui-calendar.min.js',
'/calendar/export.js',
'/calendar/recurrence.js',
'/common/recurrence.js',
'/lib/datepicker/flatpickr.js',
'tui-date-picker',

View File

@ -1,908 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Util) => {
var Rec = {};
const window = globalThis;
var debug = function () {};
// Get week number with any "WKST" (firts day of the week)
// Week 1 is the first week of the year containing at least 4 days in this year
// It depends on which day is considered the first day of the week (default Monday)
// In our case, wkst is a number matching the JS rule: 0 == Sunday
var getWeekNo = Rec.getWeekNo = function (date, wkst) {
if (typeof(wkst) !== "number") { wkst = 1; } // Default monday
var newYear = new Date(date.getFullYear(),0,1);
var day = newYear.getDay() - wkst; //the day of week the year begins on
day = (day >= 0 ? day : day + 7);
var daynum = Math.floor((date.getTime() - newYear.getTime())/86400000) + 1;
var weeknum;
// Week 1 / week 53
if (day < 4) {
weeknum = Math.floor((daynum+day-1)/7) + 1;
if (weeknum > 52) {
var nYear = new Date(date.getFullYear() + 1,0,1);
var nday = nYear.getDay() - wkst;
nday = nday >= 0 ? nday : nday + 7;
weeknum = nday < 4 ? 1 : 53;
}
}
else {
weeknum = Math.floor((daynum+day-1)/7);
}
return weeknum;
};
var getYearDay = function (date) {
var start = new Date(date.getFullYear(), 0, 0);
var diff = (date - start) +
((start.getTimezoneOffset() - date.getTimezoneOffset()) * 60 * 1000);
var oneDay = 1000 * 60 * 60 * 24;
return Math.floor(diff / oneDay);
};
var setYearDay = function (date, day) {
if (typeof(day) !== "number" || Math.abs(day) < 1 || Math.abs(day) > 366) { return; }
if (day < 0) {
var max = getYearDay(new Date(date.getFullYear(), 11, 31));
day = max + day + 1;
}
date.setMonth(0);
date.setDate(day);
return true;
};
var getEndData = function (s, e) {
if (s > e) { return void console.error("Wrong data"); }
var days;
if (e.getFullYear() === s.getFullYear()) {
days = getYearDay(e) - getYearDay(s);
} else { // eYear < sYear
var tmp = new Date(s.getFullYear(), 11, 31);
var d1 = getYearDay(tmp) - getYearDay(s); // Number of days before December 31st
var de = getYearDay(e);
days = d1 + de;
while ((tmp.getFullYear()+1) < e.getFullYear()) {
tmp.setFullYear(tmp.getFullYear()+1);
days += getYearDay(tmp);
}
}
return {
h: e.getHours(),
m: e.getMinutes(),
days: days
};
};
var setEndData = function (s, e, data) {
e.setTime(+s);
if (!data) { return; }
e.setHours(data.h);
e.setMinutes(data.m);
e.setSeconds(0);
e.setDate(s.getDate() + data.days);
};
var DAYORDER = Rec.DAYORDER = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"];
var getDayData = function (str) {
var pos = Number(str.slice(0,-2));
var day = DAYORDER.indexOf(str.slice(-2));
return pos ? [pos, day] : day;
};
var goToFirstWeekDay = function (date, wkst) {
var d = date.getDay();
wkst = typeof(wkst) === "number" ? wkst : 1;
if (d >= wkst) {
date.setDate(date.getDate() - (d-wkst));
} else {
date.setDate(date.getDate() - (7+d-wkst));
}
};
var getDateStr = function (date) {
return date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate();
};
var FREQ = {};
FREQ['daily'] = function (s, i) {
s.setDate(s.getDate()+i);
};
FREQ['weekly'] = function (s,i) {
s.setDate(s.getDate()+(i*7));
};
FREQ['monthly'] = function (s,i) {
s.setMonth(s.getMonth()+i);
};
FREQ['yearly'] = function (s,i) {
s.setFullYear(s.getFullYear()+i);
};
// EXPAND is used to create iterations added from a BYxxx rule
// dateA is the start date and b is the number or id of the BYxxx rule item
var EXPAND = {};
EXPAND['month'] = function (dateS, origin, b) {
var oS = new Date(origin.start);
var a = dateS.getMonth() + 1;
var toAdd = (b-a+12)%12;
var m = dateS.getMonth() + toAdd;
dateS.setMonth(m);
dateS.setDate(oS.getDate());
if (dateS.getMonth() !== m) { return; } // Day 31 may move us to the next month
return true;
};
EXPAND['weekno'] = function (dateS, origin, week, rule) {
var wkst = rule && rule.wkst;
if (typeof(wkst) !== "number") { wkst = 1; } // Default monday
var oS = new Date(origin.start);
var lastD = new Date(dateS.getFullYear(), 11, 31); // December 31st
var lastW = getWeekNo(lastD, wkst); // Last week of the year is either 52 or 53
var doubleOne = lastW === 1;
if (lastW === 1) { lastW = 52; }
var a = getWeekNo(dateS, wkst);
if (!week || week > lastW) { return false; } // Week 53 may not exist this year
if (week < 0) { week = lastW + week + 1; } // Turn negative week number into positive
var toAdd = week - a;
var weekS = new Date(+dateS);
// Go to the selected week
weekS.setDate(weekS.getDate() + (toAdd * 7));
goToFirstWeekDay(weekS, wkst);
// Then make sure we are in the correct start day
var all = 'aaaaaaa'.split('').map(function (o, i) {
var date = new Date(+weekS);
date.setDate(date.getDate() + i);
if (date.getFullYear() !== dateS.getFullYear()) { return; }
return date.toLocaleDateString() !== oS.toLocaleDateString() && date;
}).filter(Boolean);
// If we're looking for week 1 and the last week is a week 1, add the days
if (week === 1 && doubleOne) {
goToFirstWeekDay(lastD, wkst);
'aaaaaaa'.split('').some(function (o, i) {
var date = new Date(+lastD);
date.setDate(date.getDate() + i);
if (date.toLocaleDateString() === oS.toLocaleDateString()) { return; }
if (date.getFullYear() > dateS.getFullYear()) { return true; }
all.push(date);
});
}
return all.length ? all : undefined;
};
EXPAND['yearday'] = function (dateS, origin, b) {
var y = dateS.getFullYear();
var state = setYearDay(dateS, b);
if (!state) { return; } // Invalid day "b"
if (dateS.getFullYear() !== y) { return; } // Day 366 make move us to the next year
return true;
};
EXPAND['monthday'] = function (dateS, origin, b, rule) {
if (typeof(b) !== "number" || Math.abs(b) < 1 || Math.abs(b) > 31) { return false; }
var setMonthDay = function (date, day) {
var m = date.getMonth();
if (day < 0) {
var tmp = new Date(date.getFullYear(), date.getMonth()+1, 0); // Last day
day = tmp.getDate() + day + 1;
}
date.setDate(day);
return date.getMonth() === m; // Don't push if day 31 moved us to the next month
};
// Monthly events
if (rule.freq === 'monthly') {
return setMonthDay(dateS, b);
}
var all = 'aaaaaaaaaaaa'.split('').map(function (o, i) {
var date = new Date(dateS.getFullYear(), i, 1);
var ok = setMonthDay(date, b);
return ok ? date : undefined;
}).filter(Boolean);
return all.length ? all : undefined;
};
EXPAND['day'] = function (dateS, origin, b, rule) {
// Here "b" can be a single day ("TU") or a position and a day ("1MO")
var day = getDayData(b);
var pos;
if (Array.isArray(day)) {
pos = day[0];
day = day[1];
}
var all = [];
if (![0,1,2,3,4,5,6].includes(day)) { return false; }
var filterPos = function (m) {
if (!pos) { return; }
var _all = [];
'aaaaaaaaaaaa'.split('').some(function (a, i) {
if (typeof(m) !== "undefined" && i !== m) { return; }
var _pos;
var tmp = all.filter(function (d) {
return d.getMonth() === i;
});
if (pos < 0) {
_pos = tmp.length + pos;
} else {
_pos = pos - 1; // An array starts at 0 but the recurrence rule starts at 1
}
_all.push(tmp[_pos]);
return typeof(m) !== "undefined" && i === m;
});
all = _all.filter(Boolean); // The "5th" {day} won't always exist
};
var tmp;
if (rule.freq === 'yearly') {
tmp = new Date(+dateS);
var y = dateS.getFullYear();
while (tmp.getDay() !== day) { tmp.setDate(tmp.getDate()+1); }
while (tmp.getFullYear() === y) {
all.push(new Date(+tmp));
tmp.setDate(tmp.getDate()+7);
}
filterPos();
return all;
}
if (rule.freq === 'monthly') {
tmp = new Date(+dateS);
var m = dateS.getMonth();
while (tmp.getDay() !== day) { tmp.setDate(tmp.getDate()+1); }
while (tmp.getMonth() === m) {
all.push(new Date(+tmp));
tmp.setDate(tmp.getDate()+7);
}
filterPos(m);
return all;
}
if (rule.freq === 'weekly') {
while (dateS.getDay() !== day) { dateS.setDate(dateS.getDate()+1); }
}
return true;
};
var LIMIT = {};
LIMIT['month'] = function (events, rule) {
return events.filter(function (s) {
return rule.includes(s.getMonth()+1);
});
};
LIMIT['weekno'] = function (events, weeks, rules) {
return events.filter(function (s) {
var wkst = rules && rules.wkst;
if (typeof(wkst) !== "number") { wkst = 1; } // Default monday
var lastD = new Date(s.getFullYear(), 11, 31); // December 31st
var lastW = getWeekNo(lastD, wkst); // Last week of the year is either 52 or 53
if (lastW === 1) { lastW = 52; }
var w = getWeekNo(s, wkst);
return weeks.some(function (week) {
if (week > 0) { return week === w; }
return w === (lastW + week + 1);
});
});
};
LIMIT['yearday'] = function (events, days) {
return events.filter(function (s) {
var d = getYearDay(s);
var max = getYearDay(new Date(s.getFullYear(), 11, 31));
return days.some(function (day) {
if (day > 0) { return day === d; }
return d === (max + day + 1);
});
});
};
LIMIT['monthday'] = function (events, rule) {
return events.filter(function (s) {
var r = Util.clone(rule);
// Transform the negative monthdays into positive for this specific month
r = r.map(function (b) {
if (b < 0) {
var tmp = new Date(s.getFullYear(), s.getMonth()+1, 0); // Last day
b = tmp.getDate() + b + 1;
}
return b;
});
return r.includes(s.getDate());
});
};
LIMIT['day'] = function (events, days, rules) {
return events.filter(function (s) {
var dayStr = s.toLocaleDateString();
// Check how to handle position in BYDAY rules (last day of the month or the year?)
var type = 'yearly';
if (rules.freq === 'monthly' ||
(rules.freq === 'yearly' && rules.by && rules.by.month)) {
type = 'monthly';
}
// Check if this event matches one of the allowed days
return days.some(function (r) {
// rule elements are strings with pos and day
var day = getDayData(r);
var pos;
if (Array.isArray(day)) {
pos = day[0];
day = day[1];
}
if (!pos) {
return s.getDay() === day;
}
// If we have a position, we can use EXPAND.day to get the nth {day} of the
// year/month and compare if it matches with
var d = new Date(s.getFullYear(), s.getMonth(), 1);
if (type === 'yearly') { d.setMonth(0); }
var res = EXPAND["day"](d, {}, r, {freq: type});
return res.some(function (date) {
return date.toLocaleDateString() === dayStr;
});
});
});
};
LIMIT['setpos'] = function (events, rule) {
var init = events.slice();
var rules = Util.deduplicateString(rule.slice().map(function (n) {
if (n > 0) { return (n-1); }
if (n === 0) { return; }
return init.length + n;
}));
return events.filter(function (ev) {
var idx = init.indexOf(ev);
return rules.includes(idx);
});
};
var BYORDER = ['month','weekno','yearday','monthday','day'];
var BYDAYORDER = ['month','monthday','day'];
Rec.getMonthId = function (d) {
return d.getFullYear() + '-' + d.getMonth();
};
var cache = window.CP_calendar_cache = {};
var recurringAcross = {};
Rec.resetCache = function () {
cache = window.CP_calendar_cache = {};
recurringAcross = {};
};
var iterate = function (rule, _origin, s) {
// "origin" is the original event to detect the start of BYxxx
var origin = Util.clone(_origin);
var oS = new Date(origin.start);
var id = origin.id.split('|')[0]; // Use same cache when updating recurrence rule
// "uid" is used for the cache
var uid = s.toLocaleDateString();
cache[id] = cache[id] || {};
var inter = rule.interval || 1;
var freq = rule.freq;
var all = [];
var limit = function (byrule, n) {
all = LIMIT[byrule](all, n, rule);
};
var expand = function (byrule) {
return function (n) {
// Set the start date at the beginning of the current FREQ
var _s = new Date(+s);
if (rule.freq === 'yearly') {
// January 1st
_s.setMonth(0);
_s.setDate(1);
} else if (rule.freq === 'monthly') {
_s.setDate(1);
} else if (rule.freq === 'weekly') {
goToFirstWeekDay(_s, rule.wkst);
} else if (rule.freq === 'daily') {
// We don't have < byday rules so we can't expand daily rules
}
var add = EXPAND[byrule](_s, origin, n, rule);
if (!add) { return; }
if (Array.isArray(add)) {
add = add.filter(function (dateS) {
return dateS.toLocaleDateString() !== oS.toLocaleDateString();
});
Array.prototype.push.apply(all, add);
} else {
if (_s.toLocaleDateString() === oS.toLocaleDateString()) { return; }
all.push(_s);
}
};
};
// Manage interval for the next iteration
var it = Util.once(function () {
FREQ[freq](s, inter);
});
var addDefault = function () {
if (freq === "monthly") {
s.setDate(15);
} else if (freq === "yearly" && oS.getMonth() === 1 && oS.getDate() === 29) {
s.setDate(28);
}
it();
var _s = new Date(+s);
if (freq === "monthly" || freq === "yearly") {
_s.setDate(oS.getDate());
if (_s.getDate() !== oS.getDate()) { return; } // If 31st or Feb 29th doesn't exist
if (freq === "yearly" && _s.getMonth() !== oS.getMonth()) { return; }
// FIXME if there is a recUpdate that moves the 31st to the 30th, the event
// will still only be displayed on months with 31 days
}
all.push(_s);
};
if (Array.isArray(cache[id][uid])) {
debug('Get cache', id, uid);
if (freq === "monthly") {
s.setDate(15);
} else if (freq === "yearly" && oS.getMonth() === 1 && oS.getDate() === 29) {
s.setDate(28);
}
it();
return cache[id][uid];
}
if (rule.by && freq === 'yearly') {
var order = BYORDER.slice();
var monthLimit = false;
if (rule.by.weekno || rule.by.yearday || rule.by.monthday || rule.by.day) {
order.shift();
monthLimit = true;
}
var first = true;
order.forEach(function (_order) {
var r = rule.by[_order];
if (!r) { return; }
if (first) {
r.forEach(expand(_order));
first = false;
} else if (_order === "day") {
if (rule.by.yearday || rule.by.monthday || rule.by.weekno) {
limit('day', rule.by.day);
} else {
rule.by.day.forEach(expand('day'));
}
} else {
limit(_order, r);
}
});
if (rule.by.month && monthLimit) {
limit('month', rule.by.month);
}
}
if (rule.by && freq === 'monthly') {
// We're going to compute all the entries for the coming month
if (!rule.by.monthday && !rule.by.day) {
addDefault();
} else if (rule.by.monthday) {
rule.by.monthday.forEach(expand('monthday'));
} else if (rule.by.day) {
rule.by.day.forEach(expand('day'));
}
if (rule.by.month) {
limit('month', rule.by.month);
}
if (rule.by.day && rule.by.monthday) {
limit('day', rule.by.day);
}
}
if (rule.by && freq === 'weekly') {
// We're going to compute all the entries for the coming week
if (!rule.by.day) {
addDefault();
} else {
rule.by.day.forEach(expand('day'));
}
if (rule.by.month) {
limit('month', rule.by.month);
}
}
if (rule.by && freq === 'daily') {
addDefault();
BYDAYORDER.forEach(function (_order) {
var r = rule.by[_order];
if (!r) { return; }
limit(_order, r);
});
}
all.sort(function (a, b) {
return a-b;
});
if (rule.by && rule.by.setpos) {
limit('setpos', rule.by.setpos);
}
if (!rule.by || !Object.keys(rule.by).length) {
addDefault();
} else {
it();
}
var done = [];
all = all.filter(function (newS) {
var start = new Date(+newS).toLocaleDateString();
if (done.includes(start)) { return false; }
done.push(start);
return true;
});
debug('Set cache', id, uid);
cache[id][uid] = all;
return all;
};
var getNextRules = function (obj) {
if (!obj.recUpdate) { return []; }
var _allRules = {};
var _obj = obj.recUpdate.from;
Object.keys(_obj || {}).forEach(function (d) {
var u = _obj[d];
if (u.recurrenceRule) { _allRules[d] = u.recurrenceRule; }
});
return Object.keys(_allRules).sort(function (a, b) { return Number(a)-Number(b); })
.map(function (k) {
var r = Util.clone(_allRules[k]);
if (!FREQ[r.freq]) { return; }
if (r.interval && r.interval < 1) { return; }
r._start = Number(k);
return r;
}).filter(Boolean);
};
var fixTimeZone = function (evTimeZone, origin, target) {
var getOffset = function (date, tz) {
// Get an ISO string using Canadian local format
let iso = date.toLocaleString('en-CA', { timeZone:tz, hour12: false }).replace(', ', 'T');
iso += '.' + date.getMilliseconds().toString().padStart(3, '0');
// Get a UTC version of this time
let utcDate = new Date(iso + 'Z');
// Return the difference in timestamps, as minutes (60*1000)
return -(utcDate - date);
};
var myTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
var offset = getOffset(origin, evTimeZone) - getOffset(target, evTimeZone);
var myOffset = getOffset(origin, myTimeZone) - getOffset(target, myTimeZone);
return myOffset - offset;
};
Rec.getRecurring = function (months, events) {
if (window.CP_DEV_MODE) { debug = console.warn; }
var toAdd = [];
months.forEach(function (monthId) {
// from 1st day of the month at 00:00 to last day at 23:59:59:999
var ms = monthId.split('-');
var _startMonth = new Date(ms[0], ms[1]);
var _endMonth = new Date(+_startMonth);
_endMonth.setMonth(_endMonth.getMonth() + 1);
_endMonth.setMilliseconds(-1);
debug('Compute month', _startMonth.toLocaleDateString());
var rec = events || [];
rec.forEach(function (obj) {
var _start = new Date(obj.start);
var _end = new Date(obj.end);
var _origin = obj;
var rule = obj.recurrenceRule;
if (!rule) { return; }
var nextRules = getNextRules(obj);
var nextRule = nextRules.shift();
if (_start >= _endMonth) { return; }
// Check the "until" date of the latest rule we can use and stop now
// if the recurrence ends before the current month
var until = rule.until;
var _nextRules = nextRules.slice();
var _nextRule = nextRule;
while (_nextRule && _nextRule._start && _nextRule._start < _startMonth) {
until = nextRule.until;
_nextRule = _nextRules.shift();
}
if (until < _startMonth) { return; }
var endData = getEndData(_start, _end);
if (rule.interval && rule.interval < 1) { return; }
if (!FREQ[rule.freq]) { return; }
/*
// Rule examples
rule.by = {
//month: [1, 4, 5, 8, 12],
//weekno: [1, 2, 4, 5, 32, 34, 35, 50],
//yearday: [1, 2, 29, 30, -2, -1, 250],
//monthday: [1, 2, 3, -3, -2, -1],
//day: ["MO", "WE", "FR"],
//setpos: [1, 2, -1, -2]
};
rule.wkst = 0;
rule.interval = 2;
rule.freq = 'yearly';
rule.count = 10;
*/
debug('Iterate over', obj.title, obj);
debug('Use rule', rule);
var count = rule.count;
var c = 1;
var next = function (start) {
var evS = new Date(+start);
if (count && c >= count) { return; }
debug('Start iteration', evS.toLocaleDateString());
var _toAdd = iterate(rule, obj, evS);
debug('Iteration results', JSON.stringify(_toAdd.map(function (o) { return new Date(o).toLocaleDateString();})));
// Make sure to continue if the current year doesn't provide any result
if (!_toAdd.length) {
if (evS.getFullYear() < _startMonth.getFullYear() ||
evS < _endMonth) {
return void next(evS);
}
return;
}
var stop = false;
var newrule = false;
_toAdd.some(function (_newS) {
// Make event with correct start and end time
var _ev = Util.clone(obj);
_ev.id = _origin.id + '|' + (+_newS);
var _evS = new Date(+_newS);
var _evE = new Date(+_newS);
setEndData(_evS, _evE, endData);
_ev.start = +_evS;
_ev.end = +_evE;
_ev._count = c;
if (_ev.isAllDay && _ev.startDay) { _ev.startDay = getDateStr(_evS); }
if (_ev.isAllDay && _ev.endDay) { _ev.endDay = getDateStr(_evE); }
if (nextRule && _ev.start === nextRule._start) {
newrule = true;
}
var useNewRule = function () {
if (!newrule) { return; }
debug('Use new rule', nextRule);
_ev._count = c;
count = nextRule.count;
c = 1;
evS = +_evS;
obj = _ev;
rule = nextRule;
nextRule = nextRules.shift();
};
if (c >= count) { // Limit reached
debug(_evS.toLocaleDateString(), 'count');
stop = true;
return true;
}
if (_evS >= _endMonth) { // Won't affect us anymore
debug(_evS.toLocaleDateString(), 'endMonth');
stop = true;
return true;
}
if (rule.until && _evS > rule.until) {
debug(_evS.toLocaleDateString(), 'until');
stop = true;
return true;
}
if (_evS < _start) { // "Expand" rules may create events before the _start
debug(_evS.toLocaleDateString(), 'start');
return;
}
c++;
if (_evE < _startMonth) { // Ended before the current month
// Nothing to display but continue the recurrence
debug(_evS.toLocaleDateString(), 'startMonth');
if (newrule) { useNewRule(); }
return;
}
// If a recurring event start and end in different months, make sure
// it is only added once
if ((_evS < _endMonth && _evE >= _endMonth) ||
(_evS < _startMonth && _evE >= _startMonth)) {
if (recurringAcross[_ev.id] && recurringAcross[_ev.id].includes(_ev.start)) {
return;
} else {
recurringAcross[_ev.id] = recurringAcross[_ev.id] || [];
recurringAcross[_ev.id].push(_ev.start);
}
}
// Add this event
if (_origin.timeZone && !_ev.isAllDay) {
var offset = fixTimeZone(_origin.timeZone, _start, _evS);
_ev.start += offset;
_ev.end += offset;
}
toAdd.push(_ev);
if (newrule) {
useNewRule();
return true;
}
});
if (!stop) { next(evS); }
};
next(_start);
debug('Added this month (all events)', toAdd.map(function (ev) {
return new Date(ev.start).toLocaleDateString();
}));
});
});
return toAdd;
};
Rec.getAllOccurrences = function (ev) {
if (!ev.recurrenceRule) { return [ev.start]; }
var r = ev.recurrenceRule;
// In case of infinite recursion, we can't get all
if (!r.until && !r.count) { return false; }
var all = [ev.start];
var d = new Date(ev.start);
d.setDate(15); // Make sure we won't skip a month if the event starts on day > 28
var toAdd = [];
var i = 0;
var check = function () {
return r.count ? (all.length < r.count) : (+d <= r.until);
};
while ((toAdd = Rec.getRecurring([Rec.getMonthId(d)], [ev])) && check() && i < (r.count*12)) {
Array.prototype.push.apply(all, toAdd.map(function (_ev) { return _ev.start; }));
d.setMonth(d.getMonth() + 1);
i++;
}
return all;
};
Rec.diffDate = function (oldTime, newTime) {
var n = new Date(newTime);
var o = new Date(oldTime);
// Diff Days
var d = 0;
var mult = n < o ? -1 : 1;
while (n.toLocaleDateString() !== o.toLocaleDateString() || mult >= 10000) {
n.setDate(n.getDate() - mult);
d++;
}
d = mult * d;
// Diff hours
n = new Date(newTime);
var h = n.getHours() - o.getHours();
// Diff minutes
var m = n.getMinutes() - o.getMinutes();
return {
d: d,
h: h,
m: m
};
};
var sortUpdate = function (obj) {
return Object.keys(obj).sort(function (d1, d2) {
return Number(d1) - Number(d2);
});
};
Rec.applyUpdates = function (events) {
events.forEach(function (ev) {
ev.raw = {
start: ev.start,
end: ev.end,
};
if (!ev.recUpdate) { return; }
var from = ev.recUpdate.from || {};
var one = ev.recUpdate.one || {};
var s = ev.start;
// Add "until" date to our recurrenceRule if it has been modified in future occurences
var nextRules = getNextRules(ev).filter(function (r) {
return r._start > s;
});
var nextRule = nextRules.shift();
var applyDiff = function (obj, k) {
var diff = obj[k]; // Diff is always compared to origin start/end
var d = new Date(ev.raw[k]);
d.setDate(d.getDate() + diff.d);
d.setHours(d.getHours() + diff.h);
d.setMinutes(d.getMinutes() + diff.m);
ev[k] = +d;
};
sortUpdate(from).forEach(function (d) {
if (s < Number(d)) { return; }
Object.keys(from[d]).forEach(function (k) {
if (k === 'start' || k === 'end') { return void applyDiff(from[d], k); }
if (k === "recurrenceRule" && !from[d][k]) { return; }
ev[k] = from[d][k];
});
});
Object.keys(one[s] || {}).forEach(function (k) {
if (k === 'start' || k === 'end') { return void applyDiff(one[s], k); }
if (k === "recurrenceRule" && !one[s][k]) { return; }
ev[k] = one[s][k];
});
if (ev.deleted) {
Object.keys(ev).forEach(function (k) {
delete ev[k];
});
}
if (nextRule && ev.recurrenceRule) {
ev.recurrenceRule._next = nextRule._start - 1;
}
if (ev.reminders) {
ev.raw.reminders = ev.reminders;
}
});
return events;
};
return Rec;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(require('./common-util'));
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define(['/common/common-util.js'], factory);
} else {
// unsupported initialization
}
})();

View File

@ -15,7 +15,7 @@ define([
'/common/common-hash.js',
'/common/common-util.js',
'/common/pinpad.js',
'/common/outer/network-config.js',
'/common/network-config.js',
'/common/outer/login-block.js',
'/customize/pages.js',
'/checkup/checkup-tools.js',

View File

@ -1,47 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = function (AppConfig = {}) {
return {
setCustomize: data => {
AppConfig = data.AppConfig;
},
// localStorage
userHashKey: 'User_hash',
userNameKey: 'User_name',
blockHashKey: 'Block_hash',
fileHashKey: 'FS_hash',
sessionJWT: 'Session_JWT',
ssoSeed: 'SSO_seed',
// Store
displayNameKey: 'cryptpad.username',
oldStorageKey: 'CryptPad_RECENTPADS',
storageKey: 'filesData',
tokenKey: 'loginToken',
prefersDriveRedirectKey: 'prefersDriveRedirect',
isPremiumKey: 'isPremiumUser',
displayPadCreationScreen: 'displayPadCreationScreen',
deprecatedKey: 'deprecated',
MAX_TEAMS_SLOTS: AppConfig.maxTeamsSlots || 5,
MAX_TEAMS_OWNED: AppConfig.maxOwnedTeams || 5,
MAX_PREMIUM_TEAMS_SLOTS: Math.max(AppConfig.maxTeamsSlots || 0, AppConfig.maxPremiumTeamsSlots || 0) || 5,
MAX_PREMIUM_TEAMS_OWNED: Math.max(AppConfig.maxOwnedTeams || 0, AppConfig.maxPremiumTeamsOwned || 0) || 5,
// Apps
criticalApps: ['profile', 'settings', 'debug', 'admin', 'support', 'notifications', 'calendar', 'moderation', 'oldadmin'], // XXX oldadmin
earlyAccessApps: []
};
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(undefined);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define(['/customize/application_config.js'], factory);
} else {
// unsupported initialization
}
})();

View File

@ -1,116 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(function () {
var factory = function (AppConfig = {}, Scrypt) {
var Cred = {};
Cred.setCustomize = data => {
AppConfig = data.AppConfig;
};
Cred.MINIMUM_PASSWORD_LENGTH = typeof(AppConfig.minimumPasswordLength) === 'number'?
AppConfig.minimumPasswordLength: 8; // TODO 14 or higher is a decent default for 2023
Cred.MINIMUM_NAME_LENGTH = 1;
Cred.MAXIMUM_NAME_LENGTH = 64;
// https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript
Cred.isEmail = function (email) {
var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
};
Cred.isLongEnoughPassword = function (passwd) {
return passwd.length >= Cred.MINIMUM_PASSWORD_LENGTH;
};
var isString = Cred.isString = function (x) {
return typeof(x) === 'string';
};
// Maximum username length is enforced at registration time
// rather than in this function
// in order to maintain backwards compatibility with accounts
// that might have already registered with a longer name.
Cred.isValidUsername = function (name) {
return !!(isString(name) && name.length >= Cred.MINIMUM_NAME_LENGTH);
};
Cred.isValidPassword = function (passwd) {
return !!(passwd && isString(passwd));
};
Cred.passwordsMatch = function (a, b) {
return isString(a) && isString(b) && a === b;
};
Cred.customSalt = function () {
return typeof(AppConfig.loginSalt) === 'string'?
AppConfig.loginSalt: '';
};
Cred.deriveFromPassphrase = function (username, password, len, cb) {
Scrypt(password,
username + Cred.customSalt(), // salt
8, // memoryCost (n)
1024, // block size parameter (r)
len || 128, // dkLen
200, // interruptStep
cb,
undefined); // format, could be 'base64'
};
Cred.dispenser = function (bytes) {
var entropy = {
used: 0,
};
// crypto hygeine
var consume = function (n) {
// explode if you run out of bytes
if (entropy.used + n > bytes.length) {
throw new Error('exceeded available entropy');
}
if (typeof(n) !== 'number') { throw new Error('expected a number'); }
if (n <= 0) {
throw new Error('expected to consume a positive number of bytes');
}
// grab an unused slice of the entropy
// Note: Internet Explorer doesn't support .slice on Uint8Array
var A;
if (bytes.slice) {
A = bytes.slice(entropy.used, entropy.used + n);
} else {
A = bytes.subarray(entropy.used, entropy.used + n);
}
// account for the bytes you used so you don't reuse bytes
entropy.used += n;
//console.info("%s bytes of entropy remaining", bytes.length - entropy.used);
return A;
};
return consume;
};
return Cred;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
undefined, //require("../../customize.dist/application_config.js"),
require("scrypt-async")
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/customize/application_config.js',
'/components/scrypt-async/scrypt-async.min.js',
], function (AppConfig) {
return factory(AppConfig, window.scrypt);
});
}
}());

View File

@ -1,79 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (AppConfig = {}, Messages= {}) => {
var Feedback = {};
Feedback.setCustomize = data => {
Messages = data.Messages;
AppConfig = data.AppConfig;
};
Feedback.init = function (state) {
Feedback.state = state;
};
var randomToken = function () {
return Math.random().toString(16).replace(/0./, '');
};
var ajax = function (url, cb) {
var http = new XMLHttpRequest();
http.open('HEAD', url);
http.onreadystatechange = function() {
if (this.readyState === this.DONE) {
if (cb) { cb(); }
}
};
http.send();
};
Feedback.send = function (action, force, cb) {
if (typeof(cb) !== 'function') { cb = function () {}; }
if (AppConfig.disableFeedback) { return void cb(); }
if (!action) { return void cb(); }
if (force !== true) {
if (!Feedback.state) { return void cb(); }
}
var href = '/common/feedback.html?' + action + '=' + randomToken();
ajax(href, cb);
};
Feedback.reportAppUsage = function () {
var pattern = window.location.pathname.split('/')
.filter(function (x) { return x; }).join('.');
if (/^#\/1\/view\//.test(window.location.hash)) {
Feedback.send(pattern + '_VIEW');
} else {
Feedback.send(pattern);
}
};
Feedback.reportScreenDimensions = function () {
var h = window.innerHeight;
var w = window.innerWidth;
Feedback.send('DIMENSIONS:' + h + 'x' + w);
};
Feedback.reportLanguage = function () {
if (!Messages) { return; }
Feedback.send('LANG_' + Messages._languageUsed);
};
return Feedback;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(undefined, undefined);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/customize/application_config.js',
'/customize/messages.js'
], factory);
} else {
// unsupported initialization
}
})();

View File

@ -1,766 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(function (window) {
var factory = function (Util, Crypto, Keys, Nacl) {
var Hash = window.CryptPad_Hash = {};
var uint8ArrayToHex = Util.uint8ArrayToHex;
var hexToBase64 = Util.hexToBase64;
var base64ToHex = Util.base64ToHex;
Hash.encodeBase64 = Util.encodeBase64;
Hash.decodeBase64 = Util.decodeBase64;
// This implementation must match that on the server
// it's used for a checksum
Hash.hashChannelList = function (list) {
return Util.encodeBase64(Nacl.hash(Util
.decodeUTF8(JSON.stringify(list))));
};
Hash.generateSignPair = function () {
var ed = Nacl.sign.keyPair();
var makeSafe = function (key) {
return Crypto.b64RemoveSlashes(key).replace(/=+$/g, '');
};
return {
validateKey: Hash.encodeBase64(ed.publicKey),
signKey: Hash.encodeBase64(ed.secretKey),
safeValidateKey: makeSafe(Hash.encodeBase64(ed.publicKey)),
safeSignKey: makeSafe(Hash.encodeBase64(ed.secretKey)),
};
};
Hash.getSignPublicFromPrivate = function (edPrivateSafeStr) {
var edPrivateStr = Crypto.b64AddSlashes(edPrivateSafeStr);
var privateKey = Util.decodeBase64(edPrivateStr);
var keyPair = Nacl.sign.keyPair.fromSecretKey(privateKey);
return Util.encodeBase64(keyPair.publicKey);
};
Hash.getCurvePublicFromPrivate = function (curvePrivateSafeStr) {
var curvePrivateStr = Crypto.b64AddSlashes(curvePrivateSafeStr);
var privateKey = Util.decodeBase64(curvePrivateStr);
var keyPair = Nacl.box.keyPair.fromSecretKey(privateKey);
return Util.encodeBase64(keyPair.publicKey);
};
var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) {
var version = secret.version;
var data = secret.keys;
if (version === 0) {
return secret.channel + secret.key;
}
if (version === 1) {
if (!data.editKeyStr) { return; }
return '/1/edit/' + hexToBase64(secret.channel) +
'/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/';
}
if (version === 2) {
if (!data.editKeyStr) { return; }
var pass = secret.password ? 'p/' : '';
return '/2/' + secret.type + '/edit/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/' + pass;
}
};
var getViewHashFromKeys = Hash.getViewHashFromKeys = function (secret) {
var version = secret.version;
var data = secret.keys;
if (version === 0) { return; }
if (version === 1) {
if (!data.viewKeyStr) { return; }
return '/1/view/' + hexToBase64(secret.channel) +
'/'+Crypto.b64RemoveSlashes(data.viewKeyStr)+'/';
}
if (version === 2) {
if (!data.viewKeyStr) { return; }
var pass = secret.password ? 'p/' : '';
return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass;
}
};
Hash.getHiddenHashFromKeys = function (type, secret, opts) {
opts = opts || {};
var canEdit = (secret.keys && secret.keys.editKeyStr) || secret.key;
var mode = (!opts.view && canEdit) ? 'edit/' : 'view/';
var pass = secret.password ? 'p/' : '';
if (secret.keys && secret.keys.fileKeyStr) { mode = ''; }
var hash = '/3/' + type + '/' + mode + secret.channel + '/' + pass;
var hashData = Hash.parseTypeHash(type, hash);
if (hashData && hashData.getHash) {
return hashData.getHash(opts || {});
}
return hash;
};
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (secret) {
var version = secret.version;
var data = secret.keys;
if (version === 0) { return; }
if (version === 1) {
return '/1/' + hexToBase64(secret.channel) + '/' +
Crypto.b64RemoveSlashes(data.fileKeyStr) + '/';
}
if (version === 2) {
if (!data.fileKeyStr) { return; }
var pass = secret.password ? 'p/' : '';
return '/2/' + secret.type + '/' + Crypto.b64RemoveSlashes(data.fileKeyStr) + '/' + pass;
}
};
Hash.getPublicSigningKeyString = Keys.serialize;
var fixDuplicateSlashes = function (s) {
return s.replace(/\/+/g, '/');
};
Hash.ephemeralChannelLength = 34;
Hash.createChannelId = function (ephemeral) {
var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(ephemeral? 17: 16));
if ([32, 34].indexOf(id.length) === -1 || /[^a-f0-9]/.test(id)) {
throw new Error('channel ids must consist of 32 hex characters');
}
return id;
};
/* Given a base64-encoded public key, deterministically derive a channel id
Used for support mailboxes
*/
Hash.getChannelIdFromKey = function (publicKey) {
if (!publicKey) { return; }
return uint8ArrayToHex(Hash.decodeBase64(publicKey).subarray(0,16));
};
/* Given a base64-encoded asymmetric private key
derive the corresponding public key
*/
Hash.getBoxPublicFromSecret = function (priv) {
if (!priv) { return; }
var u8_priv = Hash.decodeBase64(priv);
var pair = Nacl.box.keyPair.fromSecretKey(u8_priv);
return Hash.encodeBase64(pair.publicKey);
};
/* Given a base64-encoded private key and public key
check that the keys are part of a valid keypair
*/
Hash.checkBoxKeyPair = function (priv, pub) {
if (!pub || !priv) { return false; }
var u8_priv = Hash.decodeBase64(priv);
var pair = Nacl.box.keyPair.fromSecretKey(u8_priv);
return pub === Hash.encodeBase64(pair.publicKey);
};
Hash.createRandomHash = function (type, password) {
var cryptor;
if (type === 'file') {
cryptor = Crypto.createFileCryptor2(void 0, password);
return getFileHashFromKeys({
password: Boolean(password),
version: 2,
type: type,
keys: cryptor
});
}
cryptor = Crypto.createEditCryptor2(void 0, void 0, password);
return getEditHashFromKeys({
password: Boolean(password),
version: 2,
type: type,
keys: cryptor
});
};
/*
Version 0
/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy
Version 1: Add support for read-only access
/code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI
Version 2: Add support for password-protection
/code/#/2/code/edit/u5ACvxAYmhvG0FtrNn9FJQcf/p/
Version 3: Safe links
/code/#/3/code/edit/f0d8055aa640a97e7fd25020ca4e93b3/
Version 4: Data URL when not a realtime link yet (new pad or "static" app)
/login/#/4/login/newpad=eyJocmVmIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwL2NvZGUvIy8yL2NvZGUvZWRpdC91NUFDdnhBWW1odkcwRnRyTm45RklRY2YvIn0%3D/
/drive/#/4/drive/login=e30%3D/
*/
var getLoginOpts = function (hashArr) {
var k;
// Check if we have a ownerKey for this pad
hashArr.some(function (data) {
if (/^login=/.test(data)) {
k = data.slice(6);
return true;
}
});
return k || '';
};
var getNewPadOpts = function (hashArr) {
var k;
// Check if we have a ownerKey for this pad
hashArr.some(function (data) {
if (/^newpad=/.test(data)) {
k = data.slice(7);
return true;
}
});
return k || '';
};
var getVersionHash = function (hashArr) {
var k;
// Check if we have a ownerKey for this pad
hashArr.some(function (data) {
if (/^hash=/.test(data)) {
k = data.slice(5);
return true;
}
});
return k ? Crypto.b64AddSlashes(k) : '';
};
var getAuditorKey = function (hashArr) {
var k;
// Check if we have a ownerKey for this pad
hashArr.some(function (data) {
if (/^auditor=/.test(data)) {
k = data.slice(8);
return true;
}
});
return k ? Crypto.b64AddSlashes(k) : '';
};
var getOwnerKey = function (hashArr) {
var k;
// Check if we have a ownerKey for this pad
hashArr.some(function (data) {
if (data.length === 86) {
k = data;
return true;
}
});
return k;
};
var parseTypeHash = Hash.parseTypeHash = function (type, hash) {
if (!hash) { return; }
var options = [];
var parsed = {};
var hashArr = fixDuplicateSlashes(hash).split('/');
var addOptions = function () {
parsed.password = options.indexOf('p') !== -1;
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
parsed.versionHash = getVersionHash(options);
parsed.auditorKey = getAuditorKey(options);
parsed.newPadOpts = getNewPadOpts(options);
parsed.loginOpts = getLoginOpts(options);
parsed.ownerKey = getOwnerKey(options);
};
// Version 4: only login or newpad options, same for all the apps
if (hashArr[1] && hashArr[1] === '4') {
parsed.getHash = function (opts) {
if (!opts || !Object.keys(opts).length) { return ''; }
var hash = '/4/' + type + '/';
if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; }
if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; }
return hash;
};
parsed.getOptions = function () {
var options = {};
if (parsed.newPadOpts) { options.newPadOpts = parsed.newPadOpts; }
if (parsed.loginOpts) { options.loginOpts = parsed.loginOpts; }
return options;
};
parsed.version = 4;
parsed.app = hashArr[2];
options = hashArr.slice(3);
addOptions();
return parsed;
}
// The other versions depends on the type
if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) {
parsed.type = 'pad';
parsed.getHash = function () {
return hash;
};
parsed.getOptions = function () {
return {
embed: parsed.embed,
present: parsed.present,
ownerKey: parsed.ownerKey,
versionHash: parsed.versionHash,
auditorKey: parsed.auditorKey,
newPadOpts: parsed.newPadOpts,
loginOpts: parsed.loginOpts,
password: parsed.password
};
};
if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0
// Old hash
parsed.channel = hash.slice(0, 32);
parsed.key = hash.slice(32, 56);
parsed.version = 0;
return parsed;
}
// Version >= 1: more hash options
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 5).join('/') + '/';
var owner = typeof(opts.ownerKey) !== "undefined" ? opts.ownerKey : parsed.ownerKey;
if (owner) { hash += owner + '/'; }
if (parsed.password || opts.password) { hash += 'p/'; }
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
var versionHash = typeof(opts.versionHash) !== "undefined" ? opts.versionHash : parsed.versionHash;
if (versionHash) {
hash += 'hash=' + Crypto.b64RemoveSlashes(versionHash) + '/';
}
var auditorKey = typeof(opts.auditorKey) !== "undefined" ? opts.auditorKey : parsed.auditorKey;
if (auditorKey) {
hash += 'auditor=' + Crypto.b64RemoveSlashes(auditorKey) + '/';
}
if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; }
if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; }
return hash;
};
if (hashArr[1] && hashArr[1] === '1') { // Version 1
parsed.version = 1;
parsed.mode = hashArr[2];
parsed.channel = hashArr[3];
parsed.key = Crypto.b64AddSlashes(hashArr[4]);
options = hashArr.slice(5);
addOptions();
return parsed;
}
if (hashArr[1] && hashArr[1] === '2') { // Version 2
parsed.version = 2;
parsed.app = hashArr[2];
parsed.mode = hashArr[3];
parsed.key = hashArr[4];
options = hashArr.slice(5);
addOptions();
return parsed;
}
if (hashArr[1] && hashArr[1] === '3') { // Version 3: hidden hash
parsed.version = 3;
parsed.app = hashArr[2];
parsed.mode = hashArr[3];
parsed.channel = hashArr[4];
options = hashArr.slice(5);
addOptions();
return parsed;
}
return parsed;
}
parsed.getHash = function () { return hashArr.join('/'); };
if (['media', 'file'].indexOf(type) !== -1) {
parsed.type = 'file';
parsed.getOptions = function () {
return {
embed: parsed.embed,
present: parsed.present,
ownerKey: parsed.ownerKey,
newPadOpts: parsed.newPadOpts,
loginOpts: parsed.loginOpts,
password: parsed.password
};
};
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 4).join('/') + '/';
var owner = typeof(opts.ownerKey) !== "undefined" ? opts.ownerKey : parsed.ownerKey;
if (owner) { hash += owner + '/'; }
if (parsed.password || opts.password) { hash += 'p/'; }
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; }
if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; }
return hash;
};
if (hashArr[1] && hashArr[1] === '1') {
parsed.version = 1;
parsed.channel = hashArr[2].replace(/-/g, '/');
parsed.key = hashArr[3].replace(/-/g, '/');
options = hashArr.slice(4);
addOptions();
return parsed;
}
if (hashArr[1] && hashArr[1] === '2') { // Version 2
parsed.version = 2;
parsed.app = hashArr[2];
parsed.key = hashArr[3];
options = hashArr.slice(4);
addOptions();
return parsed;
}
if (hashArr[1] && hashArr[1] === '3') { // Version 3: hidden hash
parsed.version = 3;
parsed.app = hashArr[2];
parsed.channel = hashArr[3];
options = hashArr.slice(4);
addOptions();
return parsed;
}
return parsed;
}
if (['user'].indexOf(type) !== -1) {
parsed.type = 'user';
if (hashArr[1] && hashArr[1] === '1') {
parsed.version = 1;
parsed.user = hashArr[2];
parsed.pubkey = hashArr[3].replace(/-/g, '/');
return parsed;
}
return parsed;
}
if (['invite'].indexOf(type) !== -1) {
parsed.type = 'invite';
if (hashArr[1] && hashArr[1] === '2') {
parsed.version = 2;
parsed.app = hashArr[2];
parsed.mode = hashArr[3];
parsed.key = hashArr[4];
options = hashArr.slice(5);
parsed.password = options.indexOf('p') !== -1;
return parsed;
}
return parsed;
}
return;
};
var parsePadUrl = Hash.parsePadUrl = function (href) {
var patt = /^https*:\/\/([^\/]*)\/(.*?)\//i;
var ret = {};
if (!href) { return ret; }
if (href.slice(-1) !== '/' && href.slice(-1) !== '#') { href += '/'; }
href = href.replace(/\/\?[^#]+#/, '/#');
var idx;
// When we start without a hash, use version 4 links to add login or newpad options
var getHash = function (opts) {
if (!opts || !Object.keys(opts).length) { return ''; }
var hash = '/4/' + ret.type + '/';
if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; }
if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; }
return hash;
};
ret.getUrl = function (options) {
options = options || {};
var url = '/';
if (!ret.type) { return url; }
url += ret.type + '/';
// New pad with options: append the options to the hash
if (!ret.hashData && options && Object.keys(options).length) {
return url + '#' + getHash(options);
}
if (!ret.hashData) { return url; }
//if (ret.hashData.version === 0) { return url + '#' + ret.hash; }
//if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; }
var hash = ret.hashData.getHash(options);
url += '#' + hash;
return url;
};
ret.getOptions = function () {
if (!ret.hashData || !ret.hashData.getOptions) { return {}; }
return ret.hashData.getOptions();
};
if (!/^https*:\/\//.test(href)) {
// If it doesn't start with http(s), it should be a relative href
if (!/^\/($|[^\/])/.test(href)) { return ret; }
idx = href.indexOf('/#');
ret.type = href.slice(1, idx);
if (idx === -1) { return ret; }
ret.hash = href.slice(idx + 2);
ret.hashData = parseTypeHash(ret.type, ret.hash);
return ret;
}
href.replace(patt, function (a, domain, type) {
ret.domain = domain;
ret.type = type;
return '';
});
idx = href.indexOf('/#');
if (idx === -1) { return ret; }
ret.hash = href.slice(idx + 2);
ret.hashData = parseTypeHash(ret.type, ret.hash);
return ret;
};
Hash.hashToHref = function (hash, type) {
return '/' + type + '/#' + hash;
};
Hash.hrefToHash = function (href) {
var parsed = Hash.parsePadUrl(href);
return parsed.hash;
};
Hash.getRelativeHref = function (href) {
if (!href) { return; }
if (href.indexOf('#') === -1) { return; }
var parsed = parsePadUrl(href);
return '/' + parsed.type + '/#' + parsed.hash;
};
/*
* Returns all needed keys for a realtime channel
* - no argument: use the URL hash or create one if it doesn't exist
* - secretHash provided: use secretHash to find the keys
*/
Hash.getSecrets = function (type, secretHash, password) {
var secret = {};
var generate = function () {
secret.keys = Crypto.createEditCryptor2(void 0, void 0, password);
secret.channel = base64ToHex(secret.keys.chanId);
secret.version = 2;
secret.type = type;
};
if (!secretHash) {
generate();
return secret;
} else {
var parsed;
var hash;
if (secretHash) {
if (!type) { throw new Error("getSecrets with a hash requires a type parameter"); }
parsed = parseTypeHash(type, secretHash);
hash = secretHash;
}
if (hash.length === 0) {
generate();
return secret;
}
// old hash system : #{hexChanKey}{cryptKey}
// new hash system : #/{hashVersion}/{b64ChanKey}/{cryptKey}
if (parsed.version === 0) {
// Old hash
secret.channel = parsed.channel;
secret.key = parsed.key;
secret.version = 0;
} else if (parsed.version === 1) {
// New hash
secret.version = 1;
if (parsed.type === "pad") {
secret.channel = base64ToHex(parsed.channel);
if (parsed.mode === 'edit') {
secret.keys = Crypto.createEditCryptor(parsed.key);
secret.key = secret.keys.editKeyStr;
if (secret.channel.length !== 32 || secret.key.length !== 24) {
throw new Error("The channel key and/or the encryption key is invalid");
}
}
else if (parsed.mode === 'view') {
secret.keys = Crypto.createViewCryptor(parsed.key);
if (secret.channel.length !== 32) {
throw new Error("The channel key is invalid");
}
}
} else if (parsed.type === "file") {
secret.channel = base64ToHex(parsed.channel);
secret.keys = {
fileKeyStr: parsed.key,
cryptKey: Util.decodeBase64(parsed.key)
};
} else if (parsed.type === "user") {
throw new Error("User hashes can't be opened (yet)");
}
} else if (parsed.version === 2) {
// New hash
secret.version = 2;
secret.type = type;
secret.password = password;
if (parsed.type === "pad") {
if (parsed.mode === 'edit') {
secret.keys = Crypto.createEditCryptor2(parsed.key, void 0, password);
secret.channel = base64ToHex(secret.keys.chanId);
secret.key = secret.keys.editKeyStr;
if (secret.channel.length !== 32 || secret.key.length !== 24) {
throw new Error("The channel key and/or the encryption key is invalid");
}
}
else if (parsed.mode === 'view') {
secret.keys = Crypto.createViewCryptor2(parsed.key, password);
secret.channel = base64ToHex(secret.keys.chanId);
if (secret.channel.length !== 32) {
throw new Error("The channel key is invalid");
}
}
} else if (parsed.type === "file") {
secret.keys = Crypto.createFileCryptor2(parsed.key, password);
secret.channel = base64ToHex(secret.keys.chanId);
secret.key = secret.keys.fileKeyStr;
if (secret.channel.length !== 48 || secret.key.length !== 24) {
throw new Error("The channel key and/or the encryption key is invalid");
}
} else if (parsed.type === "user") {
throw new Error("User hashes can't be opened (yet)");
}
}
}
return secret;
};
Hash.getHashes = function (secret) {
var hashes = {};
secret = JSON.parse(JSON.stringify(secret));
if (!secret.keys && !secret.key) {
return hashes;
} else if (!secret.keys) {
secret.keys = {};
}
if (secret.keys.editKeyStr || (secret.version === 0 && secret.key)) {
hashes.editHash = getEditHashFromKeys(secret);
}
if (secret.keys.viewKeyStr) {
hashes.viewHash = getViewHashFromKeys(secret);
}
if (secret.keys.fileKeyStr) {
hashes.fileHash = getFileHashFromKeys(secret);
}
return hashes;
};
Hash.getFormData = function (secret, hash, password) {
secret = secret || Hash.getSecrets('form', hash, password);
var keys = secret && secret.keys;
var secondary = keys && keys.secondaryKey;
if (!secondary) { return; }
var curvePair = Nacl.box.keyPair.fromSecretKey(Util.decodeUTF8(secondary).slice(0,32));
var ret = {};
ret.form_public = Util.encodeBase64(curvePair.publicKey);
var privateKey = ret.form_private = Util.encodeBase64(curvePair.secretKey);
var auditorHash = Hash.getViewHashFromKeys({
version: 1,
channel: secret.channel,
keys: { viewKeyStr: Util.encodeBase64(keys.cryptKey) }
});
var _parsed = Hash.parseTypeHash('pad', auditorHash);
ret.form_auditorHash = _parsed.getHash({auditorKey: privateKey});
return ret;
};
// STORAGE
Hash.hrefToHexChannelId = function (href, password) {
var parsed = Hash.parsePadUrl(href);
if (!parsed || !parsed.hash) { return; }
var secret = Hash.getSecrets(parsed.type, parsed.hash, password);
return secret.channel;
};
Hash.getBlobPathFromHex = function (id) {
return '/blob/' + id.slice(0,2) + '/' + id;
};
Hash.serializeHash = function (hash) {
if (hash && hash.slice(-1) !== "/") { hash += "/"; }
return hash;
};
Hash.createInviteUrl = function (curvePublic, channel) {
channel = channel || Hash.createChannelId();
return window.location.origin + '/invite/#/1/' + channel +
'/' + curvePublic.replace(/\//g, '-') + '/';
};
Hash.isValidChannel = function (channelId) {
return /^[a-zA-Z0-9]{32,48}$/.test(channelId);
};
Hash.isValidHref = function (href) {
// Non-empty href?
if (!href) { return; }
var parsed = Hash.parsePadUrl(href);
// Can be parsed?
if (!parsed) { return; }
// Link to a CryptPad app?
if (!parsed.type) { return; }
// Valid hash?
if (parsed.hash) {
if (!parsed.hashData) { return; }
// Version should be a number
if (typeof(parsed.hashData.version) === "undefined") { return; }
// pads and files should have a base64 (or hex) key
if (parsed.hashData.type === 'pad' || parsed.hashData.type === 'file') {
if (!parsed.hashData.key && !parsed.hashData.channel) { return; }
if (parsed.hashData.key && !/^[a-zA-Z0-9+-/=]+$/.test(parsed.hashData.key)) { return; }
}
}
return parsed;
};
Hash.decodeDataOptions = function (opts) {
var b64 = decodeURIComponent(opts);
var str = Util.encodeUTF8(Util.decodeBase64(b64));
return Util.tryParse(str) || {};
};
Hash.encodeDataOptions = function (opts) {
var str = JSON.stringify(opts);
var b64 = Util.encodeBase64(Util.decodeUTF8(str));
return encodeURIComponent(b64);
};
Hash.getNewPadURL = function (href, opts) {
var parsed = Hash.parsePadUrl(href);
var options = parsed.getOptions();
options.newPadOpts = Hash.encodeDataOptions(opts);
return parsed.getUrl(options);
};
Hash.getLoginURL = function (href, opts) {
var parsed = Hash.parsePadUrl(href);
var options = parsed.getOptions();
options.loginOpts = Hash.encodeDataOptions(opts);
return parsed.getUrl(options);
};
return Hash;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require("./common-util"),
require("chainpad-crypto"),
require("./common-signing-keys"),
require("tweetnacl/nacl-fast")
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/components/chainpad-crypto/crypto.js',
'/common/common-signing-keys.js',
'/components/tweetnacl/nacl-fast.min.js'
], function (Util, Crypto, Keys) {
return factory(Util, Crypto, Keys, window.nacl);
});
} else {
// unsupported initialization
}
}(typeof(window) !== 'undefined'? window : {}));

View File

@ -6,7 +6,7 @@ define([
'chainpad-listmap',
'/components/chainpad-crypto/crypto.js',
'/common/common-util.js',
'/common/outer/network-config.js',
'/common/network-config.js',
'/common/common-credential.js',
'/components/chainpad/chainpad.dist.js',
'/common/common-realtime.js',

View File

@ -1,161 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Crypto, Hash, Util, Constants, Realtime) => {
var Msg = {};
var createData = Msg.createData = function (proxy, hash) {
var data = {
channel: hash || Hash.createChannelId(),
displayName: proxy['cryptpad.username'],
profile: proxy.profile && proxy.profile.view,
edPublic: proxy.edPublic,
curvePublic: proxy.curvePublic,
notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']),
avatar: proxy.profile && proxy.profile.avatar,
uid: proxy.uid,
};
if (hash === false) { delete data.channel; }
return data;
};
var getFriend = Msg.getFriend = function (proxy, pubkey) {
if (!pubkey) { return; }
if (pubkey === proxy.curvePublic) {
var data = createData(proxy);
delete data.channel;
return data;
}
return proxy.friends ? proxy.friends[pubkey] : undefined;
};
var getFriendList = Msg.getFriendList = function (proxy) {
if (!proxy.friends) { proxy.friends = {}; }
return proxy.friends;
};
var eachFriend = function (friends, cb) {
Object.keys(friends).forEach(function (id) {
if (id === 'me') { return; }
cb(friends[id], id, friends);
});
};
Msg.getFriendChannelsList = function (proxy) {
var list = [];
eachFriend(proxy.friends, function (friend) {
list.push(friend.channel);
});
return list;
};
Msg.declineFriendRequest = function (store, data, cb) {
store.mailbox.sendTo('DECLINE_FRIEND_REQUEST', {}, {
channel: data.notifications,
curvePublic: data.curvePublic
}, function (obj) {
cb(obj);
});
};
Msg.acceptFriendRequest = function (store, data, cb) {
var friend = getFriend(store.proxy, data.curvePublic) || {};
var myData = createData(store.proxy, friend.channel || data.channel);
store.mailbox.sendTo('ACCEPT_FRIEND_REQUEST', { user: myData }, {
channel: data.notifications,
curvePublic: data.curvePublic
}, function (obj) {
cb(obj);
});
};
Msg.addToFriendList = function (cfg, data, cb) {
var proxy = cfg.proxy;
var friends = getFriendList(proxy);
var pubKey = data.curvePublic; // todo validata data
if (pubKey === proxy.curvePublic) { return void cb("E_MYKEY"); }
friends[pubKey] = data;
Realtime.whenRealtimeSyncs(cfg.realtime, function () {
cb();
cfg.pinPads([data.channel], function (res) {
if (res.error) { console.error(res.error); }
});
});
};
Msg.updateMyData = function (store, curve) {
var myData = createData(store.proxy, false);
if (store.proxy.friends) {
store.proxy.friends.me = Util.clone(myData);
delete store.proxy.friends.me.channel;
}
if (store.modules['team']) {
store.modules['team'].updateMyData(myData);
}
var todo = function (friend) {
if (!friend || !friend.notifications) { return; }
delete friend.user;
myData.channel = friend.channel;
store.mailbox.sendTo('UPDATE_DATA', myData, {
channel: friend.notifications,
curvePublic: friend.curvePublic
}, function (obj) {
if (obj && obj.error) { console.error(obj); }
});
};
if (curve) {
var friend = getFriend(store.proxy, curve);
return void todo(friend);
}
eachFriend(store.proxy.friends || {}, todo);
};
Msg.removeFriend = function (store, curvePublic, cb) {
var proxy = store.proxy;
var friend = proxy.friends[curvePublic];
if (!friend) { return void cb({error: 'ENOENT'}); }
if (!friend.notifications) { return void cb({error: 'EINVAL'}); }
store.mailbox.sendTo('UNFRIEND', {
curvePublic: proxy.curvePublic
}, {
channel: friend.notifications,
curvePublic: friend.curvePublic
}, function (obj) {
if (obj && obj.error) {
return void cb(obj);
}
store.messenger.onFriendRemoved(curvePublic, friend.channel);
delete proxy.friends[curvePublic];
Realtime.whenRealtimeSyncs(store.realtime, function () {
cb(obj);
});
});
};
return Msg;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('chainpad-crypto'),
require('./common-hash'),
require('./common-util'),
require('./common-constants'),
require('./common-realtime')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/components/chainpad-crypto/crypto.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-constants.js',
'/common/common-realtime.js',
], factory);
} else {
// unsupported initialization
}
})();

View File

@ -1,36 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = () => {
var common = {};
/*
TODO make this not blow up when disconnected or lagging...
*/
common.whenRealtimeSyncs = function (realtime, cb) {
if (typeof(realtime.getAuthDoc) !== 'function') {
return void console.error('improper use of this function');
}
setTimeout(function () {
if (realtime.getAuthDoc() === realtime.getUserDoc()) {
return void cb();
} else {
realtime.onSettle(cb);
}
}, 0);
};
return common;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory();
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([], factory);
} else {
// unsupported initialization
}
})();

View File

@ -1,110 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(function () {
var factory = function () {
var Keys = {};
var unescape = function (s) {
return s.replace(/-/g, '/');
};
/* Parse the new format of "Signing Public Keys".
If anything about the input is found to be invalid, return;
this will fall back to the old parsing method
*/
var parseNewUser = function (userString) {
if (!/^\[.*?@.*\]$/.test(userString)) { return; }
var temp = userString.slice(1, -1);
var domain, username, pubkey;
temp = temp
.replace(/\/([a-zA-Z0-9+-]{43}=)$/, function (all, k) {
pubkey = unescape(k);
return '';
});
if (!pubkey) { return; }
var index = temp.lastIndexOf('@');
if (index < 1) { return; }
domain = temp.slice(index + 1);
username = temp.slice(0, index);
return {
domain: domain,
user: username,
pubkey: pubkey
};
};
var isValidUser = function (parsed) {
if (!parsed) { return; }
if (!(parsed.domain && parsed.user && parsed.pubkey)) { return; }
return true;
};
Keys.parseUser = function (user) {
var parsed = parseNewUser(user);
if (isValidUser(parsed)) { return parsed; }
var domain, username, pubkey;
user.replace(/^https*:\/\/([^\/]+)\/user\/#\/1\/([^\/]+)\/([a-zA-Z0-9+-]{43}=)$/,
function (a, d, u, k) {
domain = d;
username = u;
pubkey = unescape(k);
return '';
});
if (!domain) { throw new Error("Could not parse user id [" + user + "]"); }
return {
domain: domain,
user: username,
pubkey: pubkey
};
};
/*
0. usernames may contain spaces or many other wacky characters, so enclose the whole thing in square braces so we know its boundaries. If the formatted string does not include these we know it is either a _v1 public key string_ or _an incomplete string_. Start parsing by removing them.
1. public keys should have a fixed length, so slice them off of the end of the string.
2. domains cannot include `@`, so find the last occurence of it in the signing key and slice everything thereafter.
3. the username is everything before the `@`.
*/
Keys.serialize = function (origin, username, pubkey) {
return '[' +
username +
'@' +
origin.replace(/https*:\/\//, '') +
'/' +
pubkey.replace(/\//g, '-') +
']';
// return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
};
Keys.canonicalize = function (input) {
if (typeof(input) !== 'string') { return; }
// key is already in simple form. ensure that it is an 'unsafeKey'
if (input.length === 44) {
return unescape(input);
}
try {
return Keys.parseUser(input).pubkey;
} catch (err) {
return;
}
};
return Keys;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory();
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([], factory);
}
}());

View File

@ -1,882 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(function (window) {
const factory = (NaclUtil) => {
var Util = window.CryptPad_Util = {};
// polyfill for atob in case you're using this from node...
window.atob = window.atob || function (str) { return Buffer.from(str, 'base64').toString('binary'); };
window.btoa = window.btoa || function (str) { return Buffer.from(str, 'binary').toString('base64'); };
Util.encodeBase64 = NaclUtil.encodeBase64;
Util.decodeBase64 = str => {
let i = str.length % 4;
if (i) { str += '='.repeat(4-i); }
return NaclUtil.decodeBase64(str);
};
Util.encodeUTF8 = NaclUtil.encodeUTF8;
Util.decodeUTF8 = NaclUtil.decodeUTF8;
Util.slice = function (A, start, end) {
return Array.prototype.slice.call(A, start, end);
};
Util.u8ToBase64 = (u8, cb) => {
const reader = new FileReader();
reader.onload = () => {
let res = reader.result;
let trim = res.slice(res.indexOf(',') + 1);
cb(trim);
};
reader.readAsDataURL(new Blob([u8]));
};
Util.shuffleArray = function (a) {
for (var i = a.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
};
Util.bake = function (f, args) {
if (typeof(args) === 'undefined') { args = []; }
if (!Array.isArray(args)) { args = [args]; }
return function () {
return f.apply(null, args);
};
};
Util.both = function (pre, post) {
if (typeof(pre) !== 'function') { throw new Error('INVALID_USAGE'); }
if (typeof(post) !== 'function') { post = function (x) { return x; }; }
return function () {
pre.apply(null, arguments);
return post.apply(null, arguments);
};
};
Util.clone = function (o) {
if (o === undefined || o === null) { return o; }
return JSON.parse(JSON.stringify(o));
};
Util.serializeError = function (err) {
if (!(err instanceof Error)) { return err; }
var ser = {};
Object.getOwnPropertyNames(err).forEach(function (key) {
ser[key] = err[key];
});
return ser;
};
Util.tryParse = function (s) {
try { return JSON.parse(s); } catch (e) { return;}
};
Util.mkAsync = function (f, ms) {
if (typeof(f) !== 'function') {
throw new Error('EXPECTED_FUNCTION');
}
return function () {
var args = Array.prototype.slice.call(arguments);
setTimeout(function () {
f.apply(null, args);
}, ms);
};
};
// If once is true, after the event has been fired, any further handlers which are
// registered will fire immediately, and this type of event cannot be fired twice.
Util.mkEvent = function (once) {
var handlers = [];
var fired = false;
let promiseResolve;
const promise = new Promise(resolve => {
promiseResolve = resolve;
});
return {
reg: function (cb) {
if (once && fired) { return void setTimeout(cb); }
handlers.push(cb);
},
unreg: function (cb) {
if (handlers.indexOf(cb) === -1) {
return void console.log("event handler was already unregistered");
}
handlers.splice(handlers.indexOf(cb), 1);
},
fire: function () {
if (once && fired) { return; }
var args = Array.prototype.slice.call(arguments);
if (!fired) { promiseResolve.apply(null, args); }
fired = true;
handlers.forEach(function (h) { h.apply(null, args); });
},
// Since a promise can only resolve once only the 1st call to fire() is reflected here. Even is `once` is `false`.
promise
};
};
Util.mkTimeout = function (_f, ms) {
ms = ms || 0;
var f = Util.once(_f);
var timeout = setTimeout(function () {
f('TIMEOUT');
}, ms);
return Util.both(f, function () {
clearTimeout(timeout);
});
};
Util.onClickEnter = function ($element, handler, cfg) {
$element.on('click keydown', function (e) {
var isClick = e.type === 'click';
var isEnter = e.type === 'keydown' && e.which === 13;
var isSpace = e.type === 'keydown' && e.which === 32 && cfg && cfg.space;
if (!isClick && !isEnter && !isSpace) { return; }
// "enter" on a button triggers a click, disable it
if (e.type === 'keydown') { e.preventDefault(); }
handler(e);
});
};
Util.response = function (errorHandler) {
var pending = {};
var timeouts = {};
if (typeof(errorHandler) !== 'function') {
errorHandler = function (label) {
throw new Error(label);
};
}
var clear = function (id) {
clearTimeout(timeouts[id]);
delete timeouts[id];
delete pending[id];
};
var expect = function (id, fn, ms) {
if (typeof(id) !== 'string') { errorHandler('EXPECTED_STRING'); }
if (typeof(fn) !== 'function') { errorHandler('EXPECTED_CALLBACK'); }
pending[id] = fn;
if (typeof(ms) === 'number' && ms) {
timeouts[id] = setTimeout(function () {
if (typeof(pending[id]) === 'function') { pending[id]('TIMEOUT'); }
clear(id);
}, ms);
}
};
var handle = function (id, args) {
var fn = pending[id];
if (typeof(fn) !== 'function') {
return void errorHandler("MISSING_CALLBACK", {
id: id,
args: args,
});
}
try {
fn.apply(null, Array.isArray(args)? args : [args]);
} catch (err) {
errorHandler('HANDLER_ERROR', {
error: err,
id: id,
args: args,
});
}
clear(id);
};
return {
clear: clear,
expected: function (id) {
return Boolean(pending[id]);
},
expectation: function (id) {
return pending[id];
},
expect: expect,
handle: handle,
_pending: pending,
};
};
Util.inc = function (map, key, val) {
map[key] = (map[key] || 0) + (typeof(val) === 'number'? val: 1);
};
Util.values = function (obj) {
return Object.keys(obj).map(function (k) {
return obj[k];
});
};
Util.find = function (map, path) {
var l = path.length;
for (var i = 0; i < l; i++) {
if (typeof(map[path[i]]) === 'undefined') { return; }
map = map[path[i]];
}
return map;
};
Util.uid = function () {
return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
.toString(32).replace(/\./g, '');
};
Util.guid = function (map) {
var id = Util.uid();
// the guid (globally unique id) is valid if it does already exist in the map
if (typeof(map[id]) === 'undefined') { return id; }
// otherwise try again
return Util.guid(map);
};
Util.fixHTML = function (str) {
if (!str) { return ''; }
return str.replace(/[<>&"']/g, function (x) {
return ({ "<": "&lt;", ">": "&gt", "&": "&amp;", '"': "&#34;", "'": "&#39;" })[x];
});
};
Util.hexToBase64 = function (hex) {
var hexArray = hex
.replace(/\r|\n/g, "")
.replace(/([\da-fA-F]{2}) ?/g, "0x$1 ")
.replace(/ +$/, "")
.split(" ");
var byteString = String.fromCharCode.apply(null, hexArray);
return window.btoa(byteString).replace(/\//g, '-').replace(/=+$/, '');
};
Util.base64ToHex = function (b64String) {
var hexArray = [];
window.atob(b64String.replace(/-/g, '/')).split("").forEach(function(e){
var h = e.charCodeAt(0).toString(16);
if (h.length === 1) { h = "0"+h; }
hexArray.push(h);
});
return hexArray.join("");
};
Util.uint8ArrayToHex = function (bytes) {
var hexString = '';
for (var i = 0; i < bytes.length; i++) {
if (bytes[i] < 16) { hexString += '0'; }
hexString += bytes[i].toString(16);
}
return hexString;
};
Util.hexToUint8Array = function (hexString) {
var bytes = new Uint8Array(Math.ceil(hexString.length / 2));
for (var i = 0; i < bytes.length; i++) {
bytes[i] = parseInt(hexString.substr(i * 2, 2), 16);
}
return bytes;
};
// given an array of Uint8Arrays, return a new Array with all their values
Util.uint8ArrayJoin = function (AA) {
var l = 0;
var i = 0;
for (; i < AA.length; i++) { l += AA[i].length; }
var C = new Uint8Array(l);
i = 0;
for (var offset = 0; i < AA.length; i++) {
C.set(AA[i], offset);
offset += AA[i].length;
}
return C;
};
Util.escapeKeyCharacters = function (key) {
return key && key.replace && key.replace(/\//g, '-');
};
Util.unescapeKeyCharacters = function (key) {
return key.replace(/\-/g, '/');
};
Util.deduplicateString = function (array) {
var a = array.slice();
for(var i=0; i<a.length; i++) {
for(var j=i+1; j<a.length; j++) {
if(a[i] === a[j]) { a.splice(j--, 1); }
}
}
return a;
};
/*
* Saving files
*/
Util.fixFileName = function (filename) {
return filename.replace(/ /g, '-').replace(/[\/\?]/g, '_')
.replace(/_+/g, '_');
};
var oneKilobyte = 1024;
var oneMegabyte = 1024 * oneKilobyte;
var oneGigabyte = 1024 * oneMegabyte;
Util.bytesToGigabytes = function (bytes) {
return Math.ceil(bytes / oneGigabyte * 100) / 100;
};
Util.bytesToMegabytes = function (bytes) {
return Math.ceil(bytes / oneMegabyte * 100) / 100;
};
Util.bytesToKilobytes = function (bytes) {
return Math.ceil(bytes / oneKilobyte * 100) / 100;
};
Util.magnitudeOfBytes = function (bytes) {
if (bytes >= oneGigabyte) { return 'GB'; }
else if (bytes >= oneMegabyte) { return 'MB'; }
else { return 'KB'; }
};
// given a path, asynchronously return an arraybuffer
var getCacheKey = function (src) {
var _src = src.replace(/(\/)*$/, ''); // Remove trailing slashes
var idx = _src.lastIndexOf('/');
var cacheKey = _src.slice(idx+1);
if (!/^[a-f0-9]{48}$/.test(cacheKey)) { cacheKey = undefined; }
return cacheKey;
};
Util.getBlock = function (src, opt, cb) {
var CB = Util.once(Util.mkAsync(cb));
var headers = {};
if (typeof(opt.bearer) === 'string' && opt.bearer) {
headers.authorization = `Bearer ${opt.bearer}`;
}
fetch(src, {
method: 'GET',
credentials: 'include',
headers: headers,
}).then(response => {
if (response.ok) {
// TODO this should probably be returned as an arraybuffer or something rather than a promise
// this is resulting in some code duplication
return void CB(void 0, response);
}
if (response.status === 401 || response.status === 404) {
response.json().then((data) => {
CB(response.status, data);
}).catch(() => {
CB(response.status);
});
return;
}
CB(response.status, response);
}).catch(error => {
CB(error);
});
};
Util.fetchApi = function (origin, type, ignoreCache, cb) {
const url = new URL(origin);
url.pathname = `api/${type}`;
let href = url.href + (ignoreCache ? '?'+(+new Date()) : '');
if (typeof(self) !== "undefined" && self.crypto) {
// Browser
fetch(href).then(res => {
if (!res.ok) {
throw new Error(`Fetch error: ${res.status}`);
}
return res.text();
}).then(body => {
cb(JSON.parse(body.slice(27,-5)));
}).catch(err => {
console.error(err.message);
cb({});
});
} else if (typeof(require) !== "undefined") {
// NodeJS
const H = url.protocol === 'http:' ?
require('node:http') : require('node:https');
H.get(url.href, res => {
let body = '';
res.on('data', data => { body += data; });
res.on('end', () => {
try {
cb(JSON.parse(body.slice(27,-5)));
} catch (e) {
console.error(e);
cb({});
}
});
});
}
};
Util.fetch = function (src, cb, progress, cache) {
var CB = Util.once(Util.mkAsync(cb));
var cacheKey = getCacheKey(src);
var getBlobCache = function (id, cb) {
if (!cache || typeof(cache.getBlobCache) !== "function") { return void cb('EINVAL'); }
cache.getBlobCache(id, cb);
};
var setBlobCache = function (id, u8, cb) {
if (!cache || typeof(cache.setBlobCache) !== "function") { return void cb('EINVAL'); }
cache.setBlobCache(id, u8, cb);
};
var xhr;
var fetch = function () {
xhr = new XMLHttpRequest();
xhr.open("GET", src, true);
if (progress) {
xhr.addEventListener("progress", function (evt) {
if (evt.lengthComputable) {
var percentComplete = evt.loaded / evt.total;
progress(percentComplete);
}
}, false);
}
xhr.responseType = "arraybuffer";
xhr.onerror = function (err) { CB(err); };
xhr.onload = function () {
if (/^4/.test(''+this.status)) {
return CB('XHR_ERROR');
}
var arrayBuffer = xhr.response;
if (arrayBuffer) {
var u8 = new Uint8Array(arrayBuffer);
if (cacheKey) {
return void setBlobCache(cacheKey, u8, function () {
CB(null, u8);
});
}
return void CB(void 0, u8);
}
CB('ENOENT');
};
xhr.send(null);
};
if (!cacheKey) { return void fetch(); }
getBlobCache(cacheKey, function (err, u8) {
if (err || !u8) { return void fetch(); }
CB(void 0, u8);
});
return {
cancel: function () {
if (xhr && xhr.abort) { xhr.abort(); }
}
};
};
Util.dataURIToBlob = function (dataURI) {
var byteString = atob(dataURI.split(',')[1]);
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to an ArrayBuffer
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
// write the ArrayBuffer to a blob, and you're done
var bb = new Blob([ab], {type: mimeString});
return bb;
};
Util.throttle = function (f, ms) {
var last = 0;
var to;
var args;
var defer = function (delay) {
// no timeout: run function `f` in `ms` milliseconds
// unless `g` is called again in the meantime
to = setTimeout(function () {
// wipe the current timeout handler
to = undefined;
// take the current time
var now = +new Date();
// compute time passed since `last`
var diff = now - last;
if (diff < ms) {
// don't run `f` if `g` was called since this timeout was set
// instead calculate how much further in the future your next
// timeout should be scheduled
return void defer(ms - diff);
}
// else run `f` with the most recently supplied arguments
f.apply(null, args);
}, delay);
};
var g = function () {
// every time you call this function store the time
last = +new Date();
// remember what arguments were passed
args = Util.slice(arguments);
// if there is a pending timeout then do nothing
if (to) { return; }
defer(ms);
};
g.clear = function () {
clearTimeout(to);
to = undefined;
};
return g;
};
/* takes a function (f) and a time (t) in ms. returns a function wrapper
which prevents the internal function from being called more than once
every t ms. if the function is prevented, returns time til next valid
execution, else null.
*/
Util.notAgainForAnother = function (f, t) {
if (typeof(f) !== 'function' || typeof(t) !== 'number') {
throw new Error("invalid inputs");
}
var last = null;
return function () {
var now = +new Date();
if (last && now <= last + t) { return t - (now - last); }
last = now;
f.apply(null, Util.slice(arguments));
return null;
};
};
Util.createRandomInteger = function () {
return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
};
Util.noop = function () {};
/* for wrapping async functions such that they can only be called once */
Util.once = function (f, g) {
return function () {
if (!f) { return; }
f.apply(this, Array.prototype.slice.call(arguments));
f = g;
};
};
Util.blobToImage = function (blob, cb) {
var reader = new FileReader();
reader.onloadend = function() {
cb(reader.result);
};
reader.readAsDataURL(blob);
};
Util.blobURLToImage = function (url, cb) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
var reader = new FileReader();
reader.onloadend = function() {
cb(reader.result);
};
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.send();
};
// Check if an element is a plain object
Util.isObject = function (o) {
return typeof (o) === "object" &&
Object.prototype.toString.call(o) === '[object Object]';
};
Util.isCircular = function (o) {
try {
JSON.stringify(o);
return false;
} catch (e) { return true; }
};
/* recursively adds the properties of an object 'b' to 'a'
arrays are only shallow copies, so references to the original
might still be present. Be mindful if you will modify 'a' in the future */
Util.extend = function (a, b) {
if (!Util.isObject(a) || !Util.isObject(b)) {
return void console.log("Extend only works with 2 objects");
}
if (Util.isCircular(b)) {
return void console.log("Extend doesn't accept circular objects");
}
for (var k in b) {
if (Util.isObject(b[k])) {
a[k] = Util.isObject(a[k]) ? a[k] : {};
Util.extend(a[k], b[k]);
continue;
}
if (Array.isArray(b[k])) {
a[k] = b[k].slice();
continue;
}
a[k] = b[k];
}
};
Util.isChecked = function (el) {
// could be nothing...
if (!el) { return false; }
// check if it's a dom element
if (typeof(el.tagName) !== 'undefined') {
return Boolean(el.checked);
}
// sketchy test to see if it's jquery
if (typeof(el.prop) === 'function') {
return Boolean(el.prop('checked'));
}
// else just say it's not checked
return false;
};
Util.hexToRGB = function (hex) {
var h = hex.replace(/^#/, '');
return [
parseInt(h.slice(0,2), 16),
parseInt(h.slice(2,4), 16),
parseInt(h.slice(4,6), 16),
];
};
Util.rgbToHex = function (rgb) {
return `#${rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/).slice(1).map(n => parseInt(n, 10).toString(16).padStart(2, '0')).join('')}`;
};
Util.isSmallScreen = function () {
return window.innerHeight < 800 || window.innerWidth < 800;
};
Util.stripTags = function (text) {
var div = document.createElement("div");
div.innerHTML = text;
return div.innerText;
};
// return an object containing {name, ext}
// or {} if the name could not be parsed
Util.parseFilename = function (filename) {
if (!filename || !filename.trim()) { return {}; }
var parsedName = /^(\.?.+?)(\.[^.]+)?$/.exec(filename) || [];
return {
name: parsedName[1],
ext: parsedName[2],
};
};
// Tell if a file is plain text from its metadata={title, fileType}
Util.isPlainTextFile = function (type, name) {
// does its type begins with "text/"
if (type && type.indexOf("text/") === 0) { return true; }
// no type and no file extension -> let's guess it's plain text
var parsedName = Util.parseFilename(name);
if (!type && name && !parsedName.ext) { return true; }
// other exceptions
if (type === 'application/x-javascript') { return true; }
if (type === 'application/xml') { return true; }
return false;
};
// Tell if a file is spreadsheet from its metadata={title, fileType}
Util.isSpreadsheet = function (type, name) {
return (type &&
(type === 'application/vnd.oasis.opendocument.spreadsheet' ||
type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'))
|| (name && (name.endsWith('.xlsx') || name.endsWith('.ods')));
};
Util.isOfficeDoc = function (type, name) {
return (type &&
(type === 'application/vnd.oasis.opendocument.text' ||
type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'))
|| (name && (name.endsWith('.docx') || name.endsWith('.odt')));
};
Util.isPresentation = function (type, name) {
return (type &&
(type === 'application/vnd.oasis.opendocument.presentation' ||
type === 'application/vnd.openxmlformats-officedocument.presentationml.presentation'))
|| (name && (name.endsWith('.pptx') || name.endsWith('.odp')));
};
Util.isValidURL = function (str) {
var pattern = new RegExp('^(https?:\\/\\/)'+ // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?'); // query string
//'(\\#[-a-z\\d_]*)?$','i'); // fragment locator
return !!pattern.test(str);
};
var emoji_patt = /([\uD800-\uDBFF][\uDC00-\uDFFF])/;
var isEmoji = function (str) {
return emoji_patt.test(str);
};
var emojiStringToArray = function (str) {
var split = str.split(emoji_patt);
var arr = [];
for (var i=0; i<split.length; i++) {
var char = split[i];
if (char !== "") {
arr.push(char);
}
}
return arr;
};
Util.getFirstCharacter = function (str) {
if (!str || !str.trim()) { return '?'; }
var emojis = emojiStringToArray(str);
return isEmoji(emojis[0])? emojis[0]: str[0];
};
Util.getRandomColor = function (light) {
var getColor = function () {
if (light) {
return Math.floor(Math.random() * 156) + 70;
}
return Math.floor(Math.random() * 200) + 25;
};
return '#' + getColor().toString(16) +
getColor().toString(16) +
getColor().toString(16);
};
Util.checkRestrictedApp = function (app, AppConfig, earlyTypes, plan, loggedIn) {
// If this is an early access app, make sure this instance allows them
if (Array.isArray(earlyTypes) && earlyTypes.includes(app) && !AppConfig.enableEarlyAccess) {
return -2;
}
var premiumTypes = AppConfig.premiumTypes;
// If this is not a premium app, don't disable it
if (!Array.isArray(premiumTypes) || !premiumTypes.includes(app)) { return 2; }
// This is a premium app
// if you're not logged in, disable it
if (!loggedIn) { return -1; }
// if you're logged in, enable it only if you're a premium user
return plan ? 1 : 0;
};
/* Chrome 92 dropped support for SharedArrayBuffer in cross-origin contexts
where window.crossOriginIsolated is false.
Their blog (https://blog.chromium.org/2021/02/restriction-on-sharedarraybuffers.html)
isn't clear about why they're doing this, but since it's related to site-isolation
it seems they're trying to do vague security things.
In any case, there seems to be a workaround where you can still create them
by using `new WebAssembly.Memory({shared: true, ...})` instead of `new SharedArrayBuffer`.
This seems unreliable, but it's better than not being able to export, since
we actively rely on postMessage between iframes and therefore can't afford
to opt for full isolation.
*/
var supportsSharedArrayBuffers = function () {
try {
return Object.prototype.toString.call(new window.WebAssembly.Memory({shared: true, initial: 0, maximum: 0}).buffer) === '[object SharedArrayBuffer]';
} catch (err) {
console.error(err);
}
return false;
};
Util.supportsWasm = function () {
return !(typeof(Atomics) === "undefined" || !supportsSharedArrayBuffers() || typeof(WebAssembly) === 'undefined');
};
//Returns an array of integers in range 0 to (length-1)
Util.getKeysArray = function (length) {
return [...Array(length).keys()];
};
Util.getVersionFromUrlArgs = urlArgs => {
let arr = /ver=([0-9.]+)(-[0-9]*)?/.exec(urlArgs);
let ver = Array.isArray(arr) && arr[1];
return ver || undefined;
};
/** Saferphore copied from the npm package:
* https://www.npmjs.com/package/saferphore (MIT license)
* because the umd definition doesn't work with rollup build
*/
Util.Saferphore = {
create: resourceCount => {
var queue = [];
var check;
var mkRa = function () {
var outerCalled = 0;
return function (func) {
if (outerCalled++) { throw new Error("returnAfter() called multiple times"); }
var called = 0;
return function () {
if (called++) {
throw new Error("returnAfter wrapped callback called multiple times");
}
if (func) { func.apply(null, arguments); }
resourceCount++;
check();
};
};
};
check = function () {
if (resourceCount < 0) { throw new Error("(resourceCount < 0) should never happen"); }
if (resourceCount === 0 || queue.length === 0) { return; }
resourceCount--;
queue.shift()(mkRa());
};
return {
take: function (func) {
queue.push(func);
check();
}
};
}
};
/* End of code copied from saferphore */
return Util;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(require('tweetnacl-util'));
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define(['/components/tweetnacl-util/nacl-util.min.js'], function () {
return factory(globalThis?.nacl?.util);
});
} else {
// Unsupported initialization
}
}(typeof(self) !== 'undefined'? self: this));

View File

@ -1,238 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Crypto, CPNetflux, Netflux, Util,
Hash, Realtime, NetConfig, Cache, Pinpad, nThen) => {
var finish = function (S, err, doc) {
if (S.done) { return; }
S.cb((err && err.error), doc, err);
S.done = true;
if (!S.hasNetwork) {
var disconnect = Util.find(S, ['network', 'disconnect']);
if (typeof(disconnect) === 'function') { disconnect(); }
}
if (S.realtime && S.realtime.stop) {
try {
S.realtime.stop();
} catch (e) { console.error(e); }
}
var abort = Util.find(S, ['session', 'realtime', 'abort']);
if (typeof(abort) === 'function') {
S.session.realtime.sync();
abort();
}
};
var makeNetwork = function (cb) {
var wsUrl = NetConfig.getWebsocketURL();
Netflux.connect(wsUrl).then(function (network) {
cb(null, network);
}, function (err) {
cb(err);
});
};
var start = function (Session, config) {
// Create a network and authenticate with all our keys if necessary,
// then start chainpad-netflux
nThen(function (waitFor) {
if (Session.hasNetwork) { return; }
makeNetwork(waitFor(function (err, network) {
if (err) { return; }
config.network = network;
}));
}).nThen(function () {
Session.realtime = CPNetflux.start(config);
});
};
var onRejected = function (config, Session, data, cb) {
// Check if we can authenticate
if (!Array.isArray(data) || !data.length || data[0].length !== 16) {
return void cb(true);
}
if (!Array.isArray(Session.accessKeys)) { return void cb(true); }
// Authenticate
config.network.historyKeeper = data[0];
nThen(function (waitFor) {
Session.accessKeys.forEach(function (obj) {
Pinpad.create(config.network, obj, waitFor(function (e) {
console.log('done', obj);
if (e) { console.error(e); }
}));
});
}).nThen(function () {
cb();
});
};
var makeConfig = function (hash, opt) {
var secret;
if (typeof(hash) === 'string') {
// We can't use cryptget with a file or a user so we can use 'pad' as hash type
secret = Hash.getSecrets('pad', hash, opt.password);
} else if (typeof(hash) === 'object') {
// we may want to just supply options directly
// and this is the easiest place to do it
secret = hash;
}
if (!secret.keys) { secret.keys = secret.key; } // support old hashses
var config = {
websocketURL: NetConfig.getWebsocketURL(opt.origin),
channel: secret.channel,
validateKey: secret.keys.validateKey || undefined,
crypto: Crypto.createEncryptor(secret.keys),
logLevel: 0,
initialState: opt.initialState,
Cache: Cache
};
return config;
};
var isObject = function (o) {
return typeof(o) === 'object';
};
var overwrite = function (a, b) {
if (!(isObject(a) && isObject(b))) { return; }
Object.keys(b).forEach(function (k) { a[k] = b[k]; });
};
var get = function (hash, cb, opt, progress) {
if (typeof(cb) !== 'function') {
throw new Error('Cryptget expects a callback');
}
opt = opt || {};
progress = progress || function () {};
var config = makeConfig(hash, opt);
var Session = {
cb: cb,
accessKeys: opt.accessKeys,
hasNetwork: Boolean(opt.network)
};
config.onRejected = function (data, cb) {
onRejected(config, Session, data, cb);
};
config.onReady = function (info) {
var rt = Session.session = info.realtime;
Session.network = info.network;
progress(1);
finish(Session, void 0, rt.getUserDoc());
};
config.onError = function (info) {
console.warn(info);
finish(Session, info);
};
config.onChannelError = function (info) {
console.error(info);
finish(Session, info);
};
config.onCacheReady = opt.onCacheReady;
// We use the new onMessage handler to compute the progress:
// we should receive 2 checkpoints max, so 100 messages max
// We're going to consider that 1 message = 1%, and we'll send 100%
// at the end
var i = 0;
config.onMessage = function () {
i++;
progress(Math.min(0.99, i/100));
};
overwrite(config, opt);
start(Session, config);
};
var put = function (hash, doc, cb, opt) {
if (typeof(cb) !== 'function') {
throw new Error('Cryptput expects a callback');
}
opt = opt || {};
var config = makeConfig(hash, opt);
var Session = {
cb: cb,
accessKeys: opt.accessKeys,
hasNetwork: Boolean(opt.network)
};
config.onRejected = function (data, cb) {
onRejected(config, Session, data, cb);
};
config.onReady = function (info) {
var realtime = Session.session = info.realtime;
Session.network = info.network;
realtime.contentUpdate(doc);
var to = setTimeout(function () {
cb(new Error("Timeout"));
}, 15000);
Realtime.whenRealtimeSyncs(realtime, function () {
clearTimeout(to);
var doc = realtime.getAuthDoc();
realtime.abort();
finish(Session, void 0, doc);
});
};
config.onChannelError = function (info) {
finish(Session, info);
};
overwrite(config, opt);
start(Session, config);
};
return {
get: get,
put: put,
};
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('chainpad-crypto'),
require('chainpad-netflux'),
require('netflux-websocket'),
require('./common-util'),
require('./common-hash'),
require('./common-realtime'),
require('./network-config'),
require('./cache-store'),
require('./pinpad'),
require('nthen'),
require('chainpad')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/components/chainpad-crypto/crypto.js',
'chainpad-netflux',
'netflux-client',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/outer/network-config.js',
'/common/outer/cache-store.js',
'/common/pinpad.js',
'/components/nthen/index.js',
'/components/chainpad/chainpad.dist.js',
], factory);
} else {
// unsupported initialization
}
})();

View File

@ -8,14 +8,13 @@ define([
'/customize/messages.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/outer/cache-store.js',
'/common/common-messaging.js',
'/common/cache-store.js',
'/common/common-constants.js',
'/common/common-feedback.js',
'/common/visible.js',
'/common/user-object.js',
'/common/outer/local-store.js',
'/common/outer/worker-channel.js',
'/common/events-channel.js',
'/common/outer/login-block.js',
'/common/common-credential.js',
'/customize/login.js',
@ -25,7 +24,7 @@ define([
'/components/nthen/index.js',
'/components/tweetnacl/nacl-fast.min.js'
], function (Config, Broadcast, Messages, Util, Hash, Cache,
Messaging, Constants, Feedback, Visible, UserObject, LocalStore, Channel, Block,
Constants, Feedback, Visible, UserObject, LocalStore, Channel, Block,
Cred, Login, Store, AppConfig, nThen) {
/* This file exposes functionality which is specific to Cryptpad, but not to
@ -310,7 +309,7 @@ define([
common.makeNetwork = function (cb) {
require([
'netflux-client',
'/common/outer/network-config.js'
'/common/network-config.js'
], function (Netflux, NetConfig) {
var wsUrl = NetConfig.getWebsocketURL();
Netflux.connect(wsUrl).then(function (network) {

View File

@ -1,201 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* eslint compat/compat: "off" */
(() => {
const factory = (ApiConfig = {}) => {
let window = globalThis;
var Module = {};
ApiConfig.requireConf = ApiConfig.requireConf || {};
Module.setCustomize = data => {
ApiConfig = data.ApiConfig;
};
var apps = ['code', 'slide', 'pad', 'kanban', 'whiteboard', 'diagram', 'sheet', 'poll', 'teams', 'form', 'doc', 'presentation'];
var app = window.location && window.location.pathname.slice(1, -1); // remove "/" at the beginnin and the end
var suffix = apps.indexOf(app) !== -1 ? '-'+app : '';
var DEFAULT_MAIN = '/customize/favicon/main-favicon' + suffix + '.png?' + ApiConfig.requireConf.urlArgs;
var DEFAULT_ALT = '/customize/favicon/alt-favicon' + suffix + '.png?' + ApiConfig.requireConf.urlArgs;
var DEFAULT_MAIN_ICO = '/customize/favicon/main-favicon' + suffix + '.ico?' + ApiConfig.requireConf.urlArgs;
var DEFAULT_ALT_ICO = '/customize/favicon/alt-favicon' + suffix + '.ico?' + ApiConfig.requireConf.urlArgs;
var document = window.document;
var isSupported = Module.isSupported = function () {
return typeof(window.Notification) === 'function' && window.isSecureContext;
};
var hasPermission = Module.hasPermission = function () {
return Notification.permission === 'granted';
};
var getPermission = Module.getPermission = function (f) {
f = f || function () {};
// "Notification.requestPermission is not a function" on Firefox 68.11.0esr
if (!Notification || typeof(Notification.requestPermission) !== 'function') { return void f(false); }
Notification.requestPermission(function (permission) {
if (permission === "granted") { f(true); }
else { f(false); }
});
};
var create = Module.create = function (msg, title, icon) {
if (document && !icon) {
var favicon = document.getElementById('favicon');
icon = favicon.getAttribute('data-main-favicon') || DEFAULT_ALT;
} else if (!icon) {
icon = DEFAULT_ALT;
}
var n = new Notification(title,{
icon: icon,
body: msg,
});
n.onclick = function () {
if (!document) { return; }
try {
parent.focus();
window.focus(); //just in case, older browsers
this.close();
} catch (e) {}
};
return n;
};
Module.system = function (msg, title, icon) {
// Let's check if the browser supports notifications
if (!isSupported()) { return; /*console.log("Notifications are not supported");*/ }
// Let's check whether notification permissions have already been granted
else if (hasPermission()) {
// If it's okay let's create a notification
return create(msg, title, icon);
}
// Otherwise, we need to ask the user for permission
else if (Notification.permission !== 'denied') {
getPermission(function (state) {
if (state) { create(msg, title, icon); }
});
}
};
var createFavicon = function () {
if (!document) {
return void console.error('document is not available in this context');
}
console.debug("creating favicon");
var attrs = {
id: 'favicon',
type: 'image/png',
rel: 'icon',
'data-main-favicon': DEFAULT_MAIN,
'data-alt-favicon': DEFAULT_ALT,
href: DEFAULT_MAIN,
};
if(!document.getElementById("favicon")) {
var fav = document.createElement('link');
Object.keys(attrs).forEach(function (k) {
fav.setAttribute(k, attrs[k]);
});
document.head.appendChild(fav);
}
if(!document.getElementById("favicon-ico")) {
var faviconLink = document.createElement('link');
attrs.href = attrs.href.replace(/\.png/g, ".ico");
attrs.id = 'favicon-ico';
attrs.type = 'image/x-icon';
Object.keys(attrs).forEach(function (k) {
faviconLink.setAttribute(k, attrs[k]);
});
document.head.appendChild(faviconLink);
}
};
if (document && !document.getElementById('favicon')) { createFavicon(); }
Module.tab = function (frequency, count) {
if (!document) {
return void console.error('document is not available in this context');
}
var key = '_pendingTabNotification';
var favicon = document.getElementById('favicon');
var faviconIco = document.getElementById('favicon-ico');
var main = DEFAULT_MAIN;
var alt = DEFAULT_ALT;
var mainIco = DEFAULT_MAIN_ICO;
var altIco = DEFAULT_ALT_ICO;
if (favicon) {
main = favicon.getAttribute('data-main-favicon') || DEFAULT_MAIN;
alt = favicon.getAttribute('data-alt-favicon') || DEFAULT_ALT;
favicon.setAttribute('href', main);
}
if (faviconIco) {
mainIco = faviconIco.getAttribute('data-main-favicon') || DEFAULT_MAIN_ICO;
altIco = faviconIco.getAttribute('data-alt-favicon') || DEFAULT_ALT_ICO;
faviconIco.setAttribute('href', mainIco);
}
var cancel = function (pending) {
// only run one tab notification at a time
if (Module[key]) {
window.clearInterval(Module[key]);
if (favicon) {
favicon.setAttribute('href', pending? alt : main);
}
if (faviconIco) {
faviconIco.setAttribute('href', pending? altIco : mainIco);
}
return true;
}
return false;
};
cancel();
var step = function () {
if (favicon) {
favicon.setAttribute('href', favicon.getAttribute('href') === main? alt : main);
}
if (faviconIco) {
faviconIco.setAttribute('href', faviconIco.getAttribute('href') === mainIco? altIco : mainIco);
}
--count;
};
Module[key] = window.setInterval(function () {
if (count > 0) { return step(); }
cancel(true);
}, frequency);
step();
return {
cancel: cancel,
};
};
return Module;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(undefined);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define(['/api/config'], factory);
} else {
// unsupported initialization
}
})();

View File

@ -1,19 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = () => {
const version = 8;
return {
currentVersionNumber: version,
currentVersion: 'v' + version
};
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory();
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([], factory);
}
})();

View File

@ -1,211 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Util, localForage) => {
let window = globalThis;
let self = globalThis;
var S = window.CryptPad_Cache = {};
var onReady = Util.mkEvent(true);
// Check if indexedDB is allowed
var allowed = false;
var disabled = false;
var supported = false;
try {
var request = window.indexedDB.open('test_db', 1);
request.onsuccess = function () {
supported = true;
allowed = supported && !disabled;
onReady.fire();
};
request.onerror = function () {
onReady.fire();
};
} catch (e) {
onReady.fire();
}
S.enable = function () {
disabled = false;
allowed = supported && !disabled;
};
S.disable = function () {
disabled = true;
allowed = supported && !disabled;
};
S.isEnabled = () => allowed;
var cache = localForage.createInstance({
driver: localForage.INDEXEDDB,
name: "cp_cache"
});
S.getBlobCache = function (id, cb) {
cb = Util.once(Util.mkAsync(cb || function () {}));
onReady.reg(function () {
if (!allowed) { return void cb('NOCACHE'); }
cache.getItem(id, function (err, obj) {
if (err || !obj || !obj.c) {
return void cb(Util.serializeError(err || 'EINVAL'));
}
cb(null, obj.c);
obj.t = +new Date();
cache.setItem(id, obj, function (err) {
if (!err) { return; }
console.error(err);
});
});
});
};
S.setBlobCache = function (id, u8, cb) {
cb = Util.once(Util.mkAsync(cb || function () {}));
onReady.reg(function () {
if (!allowed) { return void cb('NOCACHE'); }
if (!u8) { return void cb('EINVAL'); }
cache.setItem(id, {
c: u8,
t: (+new Date()) // 't' represent the "lastAccess" of this cache (get or set)
}, function (err) {
cb(Util.serializeError(err));
});
});
};
// id: channel ID or blob ID
// returns array of messages
S.getChannelCache = function (id, cb) {
cb = Util.once(Util.mkAsync(cb || function () {}));
onReady.reg(function () {
if (!allowed) { return void cb('NOCACHE'); }
cache.getItem(id, function (err, obj) {
if (err || !obj || !Array.isArray(obj.c)) {
return void cb(Util.serializeError(err || 'EINVAL'));
}
cb(null, obj);
obj.t = +new Date();
cache.setItem(id, obj, function (err) {
if (!err) { return; }
console.error(err);
});
});
});
};
// Keep the last two checkpoint + any checkpoint that may exist in the last 100 messages
// FIXME: duplicate system with sliceCpIndex from lib/hk-util.js
var checkCheckpoints = function (array) {
if (!Array.isArray(array)) { return; }
// Keep the last 100 messages
if (array.length > 100) { // FIXME this behaviour is only valid for chainpad-style documents
array.splice(0, array.length - 100);
}
// Remove every message before the first checkpoint
var firstCpIdx;
array.some(function (el, i) {
if (!el.isCheckpoint) { return; }
firstCpIdx = i;
return true;
});
array.splice(0, firstCpIdx);
};
var t = {};
S.storeCache = function (id, validateKey, val, onError) {
onError = Util.once(Util.mkAsync(onError || function () {}));
onReady.reg(function () {
// Make a throttle or use the existing one to avoid calling
// storeCache with the same array multiple times
t[id] = t[id] || Util.throttle(function (validateKey, val, onError) {
if (!allowed) { return void onError('NOCACHE'); }
if (!Array.isArray(val) || !validateKey) { return void onError('EINVAL'); }
checkCheckpoints(val);
cache.setItem(id, {
k: validateKey,
c: val,
t: (+new Date()) // 't' represent the "lastAccess" of this cache (get or set)
}, function (err) {
if (err) { onError(Util.serializeError(err)); }
});
}, 50);
t[id](validateKey, val, onError);
});
};
S.leaveChannel = function (id) {
delete t[id];
};
S.clearChannel = function (id, cb) {
cb = Util.once(Util.mkAsync(cb || function () {}));
onReady.reg(function () {
if (!allowed) { return void cb('NOCACHE'); }
cache.removeItem(id, function () {
cb();
});
});
};
S.clear = function (cb) {
cb = Util.once(Util.mkAsync(cb || function () {}));
onReady.reg(function () {
if (!allowed) { return void cb('NOCACHE'); }
cache.clear(cb);
});
};
S.getKeys = function (cb) {
cb = Util.once(Util.mkAsync(cb || function () {}));
onReady.reg(function () {
if (!allowed) { return void cb('NOCACHE'); }
cache.keys().then(function (keys) {
cb(null, keys);
}).catch(function (err) {
cb(err);
});
});
};
S.getTime = function (id, cb) {
cb = Util.once(Util.mkAsync(cb || function () {}));
onReady.reg(function () {
if (!allowed) { return void cb('NOCACHE'); }
cache.getItem(id, function (err, obj) {
if (err || !obj || !obj.c) {
return void cb(Util.serializeError(err || 'EINVAL'));
}
cb(null, obj.t);
});
});
};
self.CryptPad_clearIndexedDB = S.clear;
return S;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('./common-util'),
require('localforage'),
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/components/localforage/dist/localforage.min.js',
], factory);
} else {
// unsupported initialization
}
})();

View File

@ -1,130 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (nThen, Util, ApiConfig = {}, Nacl) => {
const getApiOrigin = function () {
if (!Object.keys(ApiConfig).length) { return; }
var url;
var unsafeOriginURL = new URL(ApiConfig.httpUnsafeOrigin);
try {
url = new URL(ApiConfig.websocketPath, ApiConfig.httpUnsafeOrigin);
url.protocol = unsafeOriginURL.protocol;
return url.origin;
} catch (err) {
console.error(err);
return ApiConfig.httpUnsafeOrigin;
}
};
var API_ORIGIN = getApiOrigin();
const setCustomize = data => {
ApiConfig = data.ApiConfig;
API_ORIGIN = getApiOrigin();
};
var clone = o => JSON.parse(JSON.stringify(o));
var randomToken = () => Util.encodeBase64(Nacl.randomBytes(24));
var postData = function (url, data, cb) {
var CB = Util.once(Util.mkAsync(cb));
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
}).then(response => {
if (response.ok) {
return void response.text().then(result => { CB(void 0, Util.tryParse(result)); }); // checkup error when using .json()
//return void response.json().then(result => { CB(void 0, result); });
}
response.json().then().then(result => {
CB(response.status, result);
});
//CB(response.status, response);
}).catch(error => {
CB(error);
});
};
var serverCommand = function (keypair, my_data, cb) {
var obj = clone(my_data);
obj.publicKey = Util.encodeBase64(keypair.publicKey);
obj.nonce = randomToken();
var href = new URL('/api/auth/', API_ORIGIN);
var txid, date;
nThen(function (w) {
// Tell the server we want to do some action
postData(href, obj, w((err, data) => {
if (err) {
w.abort();
console.error(err);
// there might be more info here
if (data) { console.error(data); }
return void cb(err);
}
// if the requested action is valid, it responds with a txid and a nonce
// bundle all that up into an object, stringify it, and sign it.
// respond with an object: {sig, txid}
if (!data.date || !data.txid) {
w.abort();
return void cb('REQUEST_REJECTED');
}
txid = data.txid;
date = data.date;
}));
}).nThen(function (w) {
var copy = clone(obj);
copy.txid = txid;
copy.date = date;
var toSign = Util.decodeUTF8(JSON.stringify(copy));
var sig = Nacl.sign.detached(toSign, keypair.secretKey);
var encoded = Util.encodeBase64(sig);
var obj2 = {
sig: encoded,
txid: txid,
};
postData(href, obj2, w((err, data) => {
if (err) {
w.abort();
console.error(err);
// there might be more info here
if (data) { console.error(data); }
return void cb("RESPONSE_REJECTED", data);
}
cb(void 0, data);
}));
});
};
serverCommand.setCustomize = setCustomize;
return serverCommand;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('nthen'),
require('./common-util'),
undefined,
require('tweetnacl/nacl-fast')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/components/nthen/index.js',
'/common/common-util.js',
'/api/config',
'/components/tweetnacl/nacl-fast.min.js',
], (nThen, Util, ApiConfig) => {
return factory(nThen, Util, ApiConfig, window.nacl);
});
} else {
// unsupported initialization
}
})();

View File

@ -5,7 +5,7 @@
define([
'/common/common-constants.js',
'/common/common-hash.js',
'/common/outer/cache-store.js',
'/common/cache-store.js',
'/components/localforage/dist/localforage.min.js',
'/customize/application_config.js',
'/common/common-util.js',

View File

@ -1,41 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (ApiConfig = {}) => {
var Config = {};
Config.setCustomize = data => {
ApiConfig = data.ApiConfig;
};
Config.getWebsocketURL = function (origin) {
var path = ApiConfig.websocketPath || '/cryptpad_websocket';
if (/^ws{1,2}:\/\//.test(path)) { return path; }
var l = new URL(origin || self?.location?.href);
if (origin) {
l.href = origin;
}
var protocol = l.protocol.replace(/http/, 'ws');
var host = l.host;
var url = protocol + '//' + host + path;
return url;
};
return Config;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory();
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/api/config'
], factory);
} else {
// unsupported initialization
}
})();

View File

@ -1,964 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(function () {
var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto, Feedback) {
var Roster = {};
// this constant is somewhat arbitrary.
// Adjust it as you like to suit performance expectations
var CHECKPOINT_INTERVAL = 25;
var TIMEOUT_INTERVAL = 30000; // TIMEOUT after 30s
/*
roster: {
state: {
members: {
user0CurveKey: {
notifications: "", // required
displayName: "", // required
role: "OWNER|ADMIN|MEMBER|VIEWER", // VIEWER if not specified
profile: "",
title: ""
},
user1CurveKey: {
...
}
},
metadata: {
// guaranteed to be strings, but may be empty
topic: '',
name: '',
avatar: '',
// anything else you use may not be defined
}
}
}
*/
var isMap = function (obj) {
return Boolean(obj && typeof(obj) === 'object' && !Array.isArray(obj));
};
var getMessageId = function (msgString) {
return msgString.slice(0, 64);
};
var canCheckpoint = function (author, members) {
// if you're here then you've received a checkpoint message
// that you don't necessarily trust.
// find the author's role from your knoweldge of the state
var role = Util.find(members, [author, 'role']);
// and check if it is 'OWNER' or 'ADMIN'
return ['OWNER', 'ADMIN'].indexOf(role) !== -1;
};
var isValidRole = function (role) {
return ['OWNER', 'ADMIN', 'MEMBER', 'VIEWER'].indexOf(role) !== -1;
};
var isSelfDowngrade = function (author, curve, role, state) {
// Make sure you want to describe yourself
var selfDescribe = author === curve && state[curve];
if (!selfDescribe) { return false; }
// ADMIN and OWNER can always update roles
// we only need to allow MEMBER to downgrade themselves to VIEWER
var authorRole = Util.find(state, [author, 'role']);
if (authorRole === "MEMBER") { return role === 'VIEWER'; }
};
var canAddRole = function (author, role, members) {
var authorRole = Util.find(members, [author, 'role']);
if (!authorRole) { return false; }
// nobody can add an invalid role
if (!isValidRole(role)) { return false; }
// owners can add any valid role they want
if (authorRole === 'OWNER') { return true; }
// admins can add other admins or members or viewers
if (authorRole === "ADMIN") { return ['ADMIN', 'MEMBER', 'VIEWER'].indexOf(role) !== -1; }
// (MEMBER, other) can't add anyone of any role
return false;
};
var isValidId = function (id) {
return typeof(id) === 'string' && id.length === 44;
};
var canDescribeTarget = function (author, curve, state) {
// you must be in the group to describe anyone
if (!state[curve]) { return false; }
// anyone can describe themself
if (author === curve && state[curve]) { return true; }
var authorRole = Util.find(state, [author, 'role']);
var targetRole = Util.find(state, [curve, 'role']);
// something is really wrong if there's no authorRole
if (!authorRole) { return false; }
// owners can do whatever they want
if (authorRole === 'OWNER') { return true; }
// admins can describe anyone escept owners
if (authorRole === 'ADMIN' && targetRole !== 'OWNER') { return true; }
// members can't describe others
return false;
};
var canRemoveRole = function (author, role, members) {
var authorRole = Util.find(members, [author, 'role']);
if (!authorRole) { return false; }
// owners can remove anyone they want
if (authorRole === 'OWNER') { return true; }
// admins can remove other admins or members
if (authorRole === "ADMIN") { return ["ADMIN", "MEMBER", "VIEWER"].indexOf(role) !== -1; }
// MEMBERS and non-members cannot remove anyone of any role
return false;
};
var canUpdateMetadata = function (author, members) {
var authorRole = Util.find(members, [author, 'role']);
return Boolean(authorRole && ['OWNER', 'ADMIN'].indexOf(authorRole) !== -1);
};
var shouldCheckpoint = function (me, ref) {
// if you can't send valid checkpoints, don't try
if (!canCheckpoint(me, ref.state.members)) { return false; }
// avoid sending checkpoints too often
// it's a balance between network constraints
// and the size of the roster's log
var since = ref.internal.sinceLastCheckpoint;
if (!since || typeof(since) !== 'number' || since < CHECKPOINT_INTERVAL) {
return false;
}
// if you can't think of any other reason not to...
return true;
};
var commands = Roster.commands = {};
/* Commands are functions with the signature
(args_any, base46_author_string, roster_map, optional_base64_message_id) => boolean
they:
* throw if any of their arguments are invalid
* return true if their application to previous state results in a change
* mutate the local account of the current state
changes to the state can be simulated locally before being sent.
if the simulation throws or returns false, don't send.
*/
// the author is trying to add someone to the roster
// owners can add any role
commands.ADD = function (args, author, roster) {
if (!isMap(args)) { throw new Error("INVALID ARGS"); }
if (!roster.internal.initialized) { throw new Error("UNITIALIZED"); }
if (typeof(roster.state.members) === 'undefined') {
throw new Error("CANNOT_ADD_TO_UNITIALIZED_ROSTER");
}
var members = roster.state.members;
// iterate over everything and make sure it is valid, throw if not
Object.keys(args).forEach(function (curve) {
// FIXME only allow valid curve keys, anything else is pollution
if (!isValidId(curve)) {
console.log(curve, curve.length);
throw new Error("INVALID_CURVE_KEY");
}
// reject commands where the members are not proper objects
if (!isMap(args[curve])) { throw new Error("INVALID_CONTENT"); }
if (members[curve]) { throw new Error("ALREADY_PRESENT"); }
var data = args[curve];
// if no role was provided, assume MEMBER
if (typeof(data.role) !== 'string') { data.role = 'MEMBER'; }
if (!canAddRole(author, data.role, members)) {
throw new Error("INSUFFICIENT_PERMISSIONS");
}
if (typeof(data.displayName) !== 'string') { throw new Error("DISPLAYNAME_REQUIRED"); }
if (typeof(data.notifications) !== 'string') { throw new Error("NOTIFICATIONS_REQUIRED"); }
});
var changed = false;
// then iterate again and apply it
Object.keys(args).forEach(function (curve) {
// this will result in a change
changed = true;
members[curve] = args[curve];
});
return changed;
};
commands.RM = function (args, author, roster) {
if (!Array.isArray(args)) { throw new Error("INVALID_ARGS"); }
if (typeof(roster.state.members) === 'undefined') {
throw new Error("CANNOT_RM_FROM_UNITIALIZED_ROSTER");
}
var members = roster.state.members;
// validate first...
args.forEach(function (curve) {
if (!isValidId(curve)) { throw new Error("INVALID_CURVE_KEY"); }
// even members can remove themselves
if (curve === author) { return; }
// but if it concerns anyone else, validate that the author has sufficient permissions
var role = members[curve].role;
if (!canRemoveRole(author, role, members)) { throw new Error("INSUFFICIENT_PERMISSIONS"); }
});
var changed = false;
args.forEach(function (curve) {
// don't try to remove something that isn't there
if (!members[curve]) { return; }
changed = true;
delete members[curve];
});
return changed;
};
commands.DESCRIBE = function (args, author, roster) {
if (!args || typeof(args) !== 'object' || Array.isArray(args)) {
throw new Error("INVALID_ARGUMENTS");
}
if (typeof(roster.state.members) === 'undefined') {
throw new Error("NOT_READY");
}
var members = roster.state.members;
// iterate over all the data and make sure it is valid, throw otherwise
Object.keys(args).forEach(function (curve) {
if (!isValidId(curve)) { throw new Error("INVALID_ID"); }
if (!members[curve]) { throw new Error("NOT_PRESENT"); }
if (!canDescribeTarget(author, curve, members)) { throw new Error("INSUFFICIENT_PERMISSIONS"); }
var data = args[curve];
if (!isMap(data)) { throw new Error("INVALID_ARGUMENTS"); }
var current = Util.clone(members[curve]);
if (typeof(data.role) === 'string') { // they're trying to change the role...
// throw if they're trying to upgrade to something greater
if (!isSelfDowngrade(author, curve, data.role, members) &&
!canAddRole(author, data.role, members)) {
throw new Error("INSUFFICIENT_PERMISSIONS");
}
}
// DESCRIBE commands must initialize a displayName if it isn't already present
if (typeof(current.displayName) !== 'string' && typeof(data.displayName) !== 'string') {
throw new Error('DISPLAYNAME_REQUIRED');
}
if (['undefined', 'string'].indexOf(typeof(data.displayName)) === -1) {
throw new Error("INVALID_DISPLAYNAME");
}
// DESCRIBE commands must initialize a mailbox channel if it isn't already present
if (typeof(current.notifications) !== 'string' && typeof(data.notifications) !== 'string') {
throw new Error('NOTIFICATIONS_REQUIRED');
}
if (['undefined', 'string'].indexOf(typeof(data.notifications)) === -1) {
throw new Error("INVALID_NOTIFICATIONS");
}
});
var changed = false;
// then do a second pass and apply it if there were changes
Object.keys(args).forEach(function (curve) {
var current = Util.clone(members[curve]);
var data = args[curve];
Object.keys(data).forEach(function (key) {
// when null is passed as new data and it wasn't considered an invalid change
// remove it from the map. This is how you delete things properly
if (typeof(current[key]) !== 'undefined' && data[key] === null) { return void delete current[key]; }
current[key] = data[key];
});
if (Sortify(current) !== Sortify(members[curve])) {
changed = true;
members[curve] = current;
}
});
return changed;
};
commands.CHECKPOINT = function (args, author, roster) {
// args: complete state
// args should be a map
if (!isMap(args)) { throw new Error("INVALID_CHECKPOINT_STATE"); }
if (!roster.internal.initialized) {
//console.log("INITIALIZING");
// either you're connecting from the beginning of the log
// or from a trusted lastKnownHash.
// Either way, initialize the roster state
roster.state = args;
var metadata = roster.state.metadata = roster.state.metadata || {};
metadata.topic = metadata.topic || '';
metadata.name = metadata.name || '';
metadata.avatar = metadata.avatar || '';
roster.internal.initialized = true;
return true;
} else if (Sortify(args) !== Sortify(roster.state)) {
// a checkpoint must reinsert the previous state
throw new Error("CHECKPOINT_DOES_NOT_MATCH_PREVIOUS_STATE");
}
// otherwise, you're iterating over the log from a previous checkpoint
// so you should know everyone's role
// owners and admins can checkpoint. members and non-members cannot
if (!canCheckpoint(author, roster.state.members)) { throw new Error("INSUFFICIENT_PERMISSIONS"); }
// set the state, and indicate that a change was made
roster.state = args;
return true;
};
var MANDATORY_METADATA_FIELDS = [
'avatar',
'name',
'topic',
];
// only admin/owner can change group metadata
commands.METADATA = function (args, author, roster) {
if (!isMap(args)) { throw new Error("INVALID_ARGS"); }
if (!canUpdateMetadata(author, roster.state.members)) { throw new Error("INSUFFICIENT_PERMISSIONS"); }
// validate inputs
Object.keys(args).forEach(function (k) {
if (args[k] === null) {
if (MANDATORY_METADATA_FIELDS.indexOf(k) === -1) { return; }
throw new Error('CANNOT_REMOVE_MANDATORY_METADATA');
}
// can't set metadata to anything other than strings
// use empty string to unset a value if you must
if (typeof(args[k]) !== 'string') { throw new Error("INVALID_ARGUMENTS"); }
});
var changed = false;
// {topic, name, avatar} are all strings...
Object.keys(args).forEach(function (k) {
if (typeof(roster.state.metadata[k]) !== 'undefined' && args[k] === null) {
changed = true;
delete roster.state.metadata[k];
}
// ignore things that won't cause changes
if (args[k] === roster.state.metadata[k]) { return; }
changed = true;
roster.state.metadata[k] = args[k];
});
return changed;
};
commands.INVITE = function (args, author, roster) {
// an invitation is created with an ephemeral curve public key
// that key is ultimately given to the user you'd like on your team
// that user can exploit their possession of the public key to remove
// the pending invitation with their actual data.
if (!isMap(args)) { throw new Error('INVALID_ARGS'); }
if (!roster.internal.initialized) { throw new Error("UNINITIALIED"); }
if (typeof(roster.state.members) === 'undefined') {
throw new Error("CANNOT+INVITE_TO_UNINITIALIED_ROSTER");
}
var members = roster.state.members;
Object.keys(args).forEach(function (curve) {
if (!isValidId(curve)) {
console.log(curve, curve.length);
throw new Error("INVALID_CURVE_KEY");
}
// reject commandws wehere the members are not proper objects
if (!isMap(args[curve])) { throw new Error("INVALID_CONTENT"); }
if (members[curve]) { throw new Error("ARLEADY_PRESENT"); }
var data = args[curve];
// if no role was provided, assume VIEWER
if (typeof(data.role) !== 'string') { data.role = "VIEWER"; }
// assume that invitations are 'pending' unless stated otherwise
if (typeof(data.pending) === 'undefined') { data.pending = true; }
if (!canAddRole(author, data.role, members)) {
throw new Error("INSUFFICIENT_PERMISSIONS");
}
if (typeof(data.displayName) !== 'string' || !data.displayName) { throw new Error("DISPLAYNAME_REQUIRED"); }
//if (typeof(data.notifications) !== 'string') { throw new Error("NOTIFICATIONS_REQUIRED"); }
});
/*
{
<ephemeralCurveKey>: {
role: ??? || 'VIEWER',
displayName: '',
pending: true,
}
}
*/
var changed = false;
Object.keys(args).forEach(function (curve) {
changed = true;
members[curve] = args[curve];
});
return changed;
};
commands.ACCEPT = function (args, author, roster) {
if (!roster.internal.initialized) { throw new Error("UNINITIALIED"); }
if (typeof(roster.state.members) === 'undefined') {
throw new Error("CANNOT_ADD_TO_UNINITIALIED_ROSTER");
}
// an ACCEPT command replaces a pending invitation's curve key with a new one
// after which the invited member can use their actual curve key to describe themselves
// the author must have been invited already...
var members = roster.state.members;
// so you must already be in the members list
if (!isMap(members[author])) { throw new Error("INSUFFICIENT_PERMISSIONS"); }
// and your membership must indicate that you are 'pending'
if (!members[author].pending) { throw new Error("ALREADY_PRESENT"); }
// args should be a string
if (typeof(args) !== 'string') { throw new Error("INVALID_ARGS"); }
// ...and a valid curve key
if (!isValidId(args)) { throw new Error("INVALID_CURVE_KEY"); }
var curve = args;
// and the curve key must not already be a member
if (typeof(members[curve]) !== 'undefined') { throw new Error("MEMBER_ALREADY_PRESENT"); }
// copy the new profile from the old one
var clone = Util.clone(members[author]);
delete clone.remaining;
delete clone.totalUses;
delete clone.inviteChannel;
delete clone.previewChannel;
members[curve] = clone;
var remaining = members[author].remaining || 1;
if (remaining === -1) { return true; } // Infinite uses, keep the link
if (remaining > 1) { // Remove 1 use
members[author].remaining = remaining - 1;
} else { // Disable link
delete members[author];
}
return true;
};
var handleCommand = function (content, author, roster) {
if (!(Array.isArray(content) && typeof(author) === 'string')) {
throw new Error("INVALID ARGUMENTS");
}
var command = content[0];
if (typeof(commands[command]) !== 'function') { throw new Error('INVALID_COMMAND'); }
return commands[command](content[1], author, roster);
};
var simulate = function (content, author, roster) {
return handleCommand(content, author, Util.clone(roster));
};
Roster.create = function (config, _cb) {
if (typeof(_cb) !== 'function') { throw new Error("EXPECTED_CALLBACK"); }
var cb = Util.once(Util.mkAsync(_cb));
if (!config.network) { return void cb("EXPECTED_NETWORK"); }
if (!config.channel || typeof(config.channel) !== 'string' || config.channel.length !== 32) { return void cb("EXPECTED_CHANNEL"); }
if (!config.keys || typeof(config.keys) !== 'object') { return void cb("EXPECTED_CRYPTO_KEYS"); }
if (!config.store) { return void cb("EXPECTED_STORE"); }
var response = Util.response(function (label, info) {
console.error('ROSTER_RESPONSE__' + label, info);
});
var store = config.store;
var keys = config.keys;
var me = keys.myCurvePublic;
var channel = config.channel;
var lastKnownHash = config.lastKnownHash || -1;
// make sure we don't send -1 (ask for full history) when we are trying to create a new team
if (config.newTeam) {
lastKnownHash = undefined;
}
var ref = {
state: {
members: { },
metadata: { },
},
internal: {
initialized: false,
sinceLastCheckpoint: 0,
lastCheckpointHash: lastKnownHash,
},
};
var roster = {};
var events = {
change: Util.mkEvent(),
checkpoint: Util.mkEvent(),
};
roster.on = function (key, handler) {
if (typeof(events[key]) !== 'object') { throw new Error("unsupported event"); }
events[key].reg(handler);
return roster;
};
roster.off = function (key, handler) {
if (typeof(events[key]) !== 'object') { throw new Error("unsupported event"); }
events[key].unreg(handler);
return roster;
};
roster.once = function (key, handler) {
if (typeof(events[key]) !== 'object') { throw new Error("unsupported event"); }
var f = function () {
handler.apply(null, Array.prototype.slice.call(arguments));
events[key].unreg(f);
};
events[key].reg(f);
return roster;
};
roster.getState = function () {
//if (!isMap(ref.state)) { return; }
return Util.clone(ref.state);
};
roster.getLastCheckpointHash = function () {
return ref.internal.lastCheckpointHash || -1;
};
var clearPendingCheckpoints = function () {
// clear any pending checkpoints you might have...
if (ref.internal.pendingCheckpointId) {
response.clear(ref.internal.pendingCheckpointId);
delete ref.internal.pendingCheckpointId;
}
clearTimeout(ref.internal.checkpointTimeout);
delete ref.internal.checkpointTimeout;
};
roster.stop = function () {
if (ref.internal.cpNetflux && typeof(ref.internal.cpNetflux.stop) === "function") {
ref.internal.cpNetflux.stop();
clearPendingCheckpoints();
} else {
console.log("FAILED TO LEAVE");
}
};
var ready = false;
var onCacheReady = function () {
if (!config.onCacheReady) { return; }
var state = ref.state;
if (!Object.keys(state.members || {}).length) {
// No member, corrupted cache
try {
ref.internal.cpNetflux.resetCache();
} catch (e) { console.error(e); }
return void config.onCacheReady({error: "CORRUPTED"});
}
config.onCacheReady(roster);
};
var onReady = function () {
//console.log("READY");
ready = true;
cb(void 0, roster);
};
// onError (deleted or expired)
// you won't be able to connect
// onMetadataUpdate
// update owners?
// deleted while you are open
// emit an event
var onChannelError = function (info) {
if (Feedback) { Feedback.send('ROSTER_CHANNEL_ERROR='+(info && info.type)); }
if (info && info.type === "EUNKNOWN") {
// chainpad-netflux should recover by itself
return;
}
if (!ready) { return void cb(info); }
console.error("CHANNEL_ERROR", info);
};
var onConnectionChange = function (info) {
if (info.state) { return; }
// Disconnect: don't send event anymore until ready
ready = false;
};
var onConnect = function (/* wc, sendMessage */) {
console.log("ROSTER CONNECTED");
};
var isReady = function () {
return Boolean(ready && me);
};
var lastHash;
var onMessage = function (msg, user, vKey, isCp , hash, author) {
// count messages received since the last checkpoint
// even if they fail to parse
if (lastHash !== hash) {
// Don't count duplicate lkh message on reconnect
ref.internal.sinceLastCheckpoint++;
}
lastHash = hash;
var parsed = Util.tryParse(msg);
if (!parsed) { return void console.error("could not parse"); }
var changed;
var error;
try {
changed = handleCommand(parsed, author, ref);
} catch (err) {
error = err.message;
}
var id = getMessageId(hash);
if (response.expected(id)) {
if (error) { return void response.handle(id, [error]); }
try {
if (!changed) {
response.handle(id, ['NO_CHANGE']);
console.log(msg);
} else {
response.handle(id, [void 0, roster.getState()]);
}
} catch (err) {
console.log('CAUGHT', err);
}
}
// if a checkpoint was successfully applied, emit an event
if (parsed[0] === 'CHECKPOINT' && changed) {
if (isReady()) { events.checkpoint.fire(hash); }
// reset the counter for messages since the last checkpoint
ref.internal.sinceLastCheckpoint = 0;
ref.internal.lastCheckpointHash = hash;
} else if (changed) {
if (isReady()) { events.change.fire(); }
}
// CHECKPOINT logic...
clearPendingCheckpoints();
if (!isReady() || !shouldCheckpoint(me, ref)) { return; }
// a random number of seconds between 5 and 25
var delay = (1000 * Math.floor(Math.random() * 20)) + 5000;
// if you're here then you can and should send a checkpoint
// but since multiple users who can and should might be online at once
// and since they'll all trigger this process at the same time...
// we want to stagger attempts at random intervals
ref.internal.checkpointTimeout = setTimeout(function () {
ref.internal.pendingCheckpointId = roster.checkpoint(function (err) {
if (err) { console.error(err); }
});
}, delay);
};
var isCacheCheckpoint = function (msg, author) {
var parsed = Util.tryParse(msg);
if (parsed[0] !== 'CHECKPOINT') { return false; }
var changed = simulate(parsed, author, ref);
return changed;
};
var metadata, crypto;
var send = function (msg, cb) {
if (!isReady()) { return void cb("NOT_READY"); }
var anon_rpc = store.anon_rpc;
if (!anon_rpc) { return void cb("ANON_RPC_NOT_READY"); }
var changed = false;
try {
// simulate the command before you send it
changed = simulate(msg, keys.myCurvePublic, ref);
} catch (err) {
return void cb(err.message);
}
if (!changed) { return void cb("NO_CHANGE"); }
var ciphertext = crypto.encrypt(Sortify(msg));
var id = getMessageId(ciphertext);
//console.log("Sending with id [%s]", id, msg);
//console.log();
response.expect(id, function (err, state) {
if (err) { return void cb(err); }
cb(void 0, state, id);
}, TIMEOUT_INTERVAL);
anon_rpc.send('WRITE_PRIVATE_MESSAGE', [
channel,
ciphertext
], function (err) {
if (err) { return response.handle(id, [err.message || err]); }
});
return id;
};
roster.init = function (_data, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (ref.internal.initialized) { return void cb("ALREADY_INITIALIZED"); }
if (!isMap(_data)) { return void cb("INVALID_ARGUMENTS"); }
var data = Util.clone(_data);
data.role = 'OWNER';
var members = {};
members[me] = data;
send([ 'CHECKPOINT', { members: members } ], cb);
};
// commands
roster.checkpoint = function (_cb) {
var cb = Util.once(Util.mkAsync(_cb));
send([ 'CHECKPOINT', Util.clone(ref.state)], cb);
};
roster.add = function (_data, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
//var state = ref.state;
if (!ref.internal.initialized) { return cb("UNINITIALIZED"); }
if (!isMap(_data)) { return void cb("INVALID_ARGUMENTS"); }
var data = Util.clone(_data);
// don't add members that are already present
// use DESCRIBE to amend
Object.keys(data).forEach(function (curve) {
if (!isValidId(curve) || isMap(ref.state.members[curve])) { return delete data[curve]; }
});
send([ 'ADD', data ], cb);
};
roster.remove = function (_data, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var state = ref.state;
if (!state) { return cb("UNINITIALIZED"); }
if (!Array.isArray(_data)) { return void cb("INVALID_ARGUMENTS"); }
var data = Util.clone(_data);
var toRemove = [];
var current = Object.keys(state.members);
data.forEach(function (curve) {
// don't try to remove elements which are not in the current state
if (current.indexOf(curve) === -1) { return; }
toRemove.push(curve);
});
send([ 'RM', toRemove ], cb);
};
roster.describe = function (_data, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var state = ref.state;
if (!state) { return cb("UNINITIALIZED"); }
if (!isMap(_data)) { return void cb("INVALID_ARGUMENTS"); }
var data = Util.clone(_data);
if (Object.keys(data).some(function (curve) {
var member = data[curve];
if (!isMap(member)) { delete data[curve]; }
// validate that you're trying to describe a user that is present
if (!isMap(state.members[curve])) { return true; }
// don't send fields that won't result in a change
Object.keys(member).forEach(function (k) {
if (member[k] === state.members[curve][k]) { delete member[k]; }
});
})) {
// returning true in the above loop indicates that something was invalid
return void cb("INVALID_ARGUMENTS");
}
send(['DESCRIBE', data], cb);
};
roster.metadata = function (_data, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var metadata = ref.state.metadata;
if (!isMap(_data)) { return void cb("INVALID_ARGUMENTS"); }
var data = Util.clone(_data);
Object.keys(data).forEach(function (k) {
if (data[k] === metadata[k]) { delete data[k]; }
});
send(['METADATA', data], cb);
};
// supports multiple invite
roster.invite = function (_data, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var state = ref.state;
if (!state) { return cb("UNINITIALIZED"); }
if (!ref.internal.initialized) { return cb("UNINITIALIZED"); }
if (!isMap(_data)) { return void cb("INVALID_ARGUMENTS"); }
var data = Util.clone(_data);
Object.keys(data).forEach(function (curve) {
if (!isValidId(curve) || isMap(ref.state.members[curve])) { return delete data[curve]; }
});
send(['INVITE', data], cb);
};
roster.accept = function (_data, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (typeof(_data) !== 'string' || !isValidId(_data)) {
return void cb("INVALID_ARGUMENTS");
}
send([ 'ACCEPT', _data ], cb);
};
nThen(function (w) {
// get metadata so we know the owners and validateKey
if (!store.anon_rpc) { return; }
store.anon_rpc.send('GET_METADATA', channel, function (err, data) {
if (err) {
w.abort();
return void console.error(err);
}
metadata = ref.internal.metadata = (data && data[0]) || undefined;
});
}).nThen(function (w) {
if (!config.keys.teamEdPublic && metadata && metadata.validateKey) {
config.keys.teamEdPublic = metadata.validateKey;
}
if (!config.keys.teamEdPublic) {
w.abort();
return void cb("NO_VALIDATE_KEY");
}
try {
crypto = Crypto.Team.createEncryptor(config.keys);
} catch (err) {
w.abort();
return void cb(err);
}
}).nThen(function () {
if (typeof(lastKnownHash) === 'string') {
console.log("Synchronizing from checkpoint");
}
ref.internal.cpNetflux = CPNetflux.start({
// if you don't have a lastKnownHash you will need the full history
// passing -1 forces the server to send all messages, otherwise
// malicious users with the signing key could send cp| messages
// and fool new users into initializing their session incorrectly
lastKnownHash: lastKnownHash,
network: config.network,
channel: config.channel,
crypto: crypto,
validateKey: config.keys.teamEdPublic,
owners: config.owners,
Cache: config.Cache,
isCacheCheckpoint: isCacheCheckpoint,
onCacheReady: onCacheReady,
onChannelError: onChannelError,
onReady: onReady,
onConnect: onConnect,
onConnectionChange: onConnectionChange,
onMessage: onMessage,
noChainPad: true,
});
});
};
return Roster;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require("../../common/common-util"),
require("../../common/common-hash"),
require('chainpad-netflux'),
require('json.sortify'),
require("nthen"),
require("chainpad-crypto"),
null // no feedback here
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
require.config({ paths: { 'json.sortify': '/components/json.sortify/dist/JSON.sortify' } });
define([
'/common/common-util.js',
'/common/common-hash.js',
'chainpad-netflux',
'json.sortify',
'/components/nthen/index.js',
'/components/chainpad-crypto/crypto.js',
'/common/common-feedback.js',
//'/components/tweetnacl/nacl-fast.min.js',
], function (Util, Hash, CPNF, Sortify, nThen, Crypto, Feedback) {
return factory.apply(null, [
Util,
Hash,
CPNF,
Sortify,
nThen,
Crypto,
Feedback
]);
});
} else {
// I'm not gonna bother supporting any other kind of instanciation
}
}());

View File

@ -1,417 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Hash, Util, UserObject, Cache,
nThen, Crypto, Listmap, ChainPad) => {
var SF = {};
/* load
create and load a proxy using listmap for a given shared folder
- config: network and "manager" (either the user one or a team manager)
- id: shared folder id
*/
var allSharedFolders = {};
// No version: visible edit
// Version 2: encrypted edit links
SF.checkMigration = function (secondaryKey, proxy, uo, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!proxy) { return void cb(); }
// View access: can't migrate
if (!secondaryKey) { return void cb(); }
// Already migrated: nothing to do
if (proxy.version >= 2) { return void cb(); }
// Not yet migrating: migrate
if (!proxy.migrateRo) { return void uo.migrateReadOnly(cb); }
// Already migrating: wait for the end...
var done = false;
var to;
var it = setInterval(function () {
if (proxy.version >= 2) {
done = true;
clearTimeout(to);
clearInterval(it);
return void cb();
}
}, 100);
to = setTimeout(function () {
clearInterval(it);
uo.migrateReadOnly(function () {
done = true;
cb();
});
}, 20000);
var path = ['version'];
proxy.on('change', path, function () {
if (done) { return; }
if (proxy.version >= 2) {
done = true;
clearTimeout(to);
clearInterval(it);
cb();
}
});
};
// SFMIGRATION: only needed if we want a manual migration from the share modal...
SF.migrate = function (channel) {
var sf = allSharedFolders[channel];
if (!sf) { return; }
var clients = sf.teams;
if (!Array.isArray(clients) || !clients.length) { return; }
var c = clients[0];
// No secondaryKey? ==> already migrated ==> abort
if (!c.secondaryKey) { return; }
var f = Util.find(c, ['store', 'manager', 'folders', c.id]);
// Can't find the folder: abort
if (!f) { return; }
// Already migrated: abort
if (!f.proxy || f.proxy.version) { return; }
f.userObject.migrateReadOnly(function () {
clients.forEach(function (obj) {
var uo = Util.find(obj, ['store', 'manager', 'folders', obj.id, 'userObject']);
uo.setReadOnly(false, obj.secondarykey);
});
});
};
SF.load = function (config, id, data, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var network = config.network;
var store = config.store;
var isNew = config.isNew;
var isNewChannel = config.isNewChannel;
var teamId = store.id;
var handler = store.handleSharedFolder;
var href = store.manager.user.userObject.getHref(data);
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets('drive', parsed.hash, data.password);
// If we don't have valid keys, abort and remove the proxy to make sure
// we don't block the drive permanently
if (!secret.keys) {
store.manager.deprecateProxy(id);
return void cb(null);
}
var secondaryKey = secret.keys.secondaryKey;
// If we try to load an existing shared folder (isNew === false) but this folder
// doesn't exist in the database, abort and cb
nThen(function (waitFor) {
// If we're in onCacheReady, make sure we have a cache for this shared folder
if (config.cache) {
Cache.getChannelCache(secret.channel, waitFor(function (err) {
if (err === "EINVAL") { // Cache not found
waitFor.abort();
store.manager.restrictedProxy(id, secret.channel);
return void cb(null);
}
}));
}
}).nThen(function (waitFor) {
isNewChannel(null, { channel: secret.channel }, waitFor(function (obj) {
if (obj.isNew && !isNew) {
store.manager.deprecateProxy(id, secret.channel, obj.reason);
waitFor.abort();
return void cb(null);
}
}));
}).nThen(function () {
var sf = allSharedFolders[secret.channel];
if (sf && sf.readOnly && secondaryKey) {
// We were in readOnly mode and now we know the edit keys!
SF.upgrade(secret.channel, secret);
}
if (sf && sf.ready && sf.rt) {
// The shared folder is already loaded, return its data
setTimeout(function () {
var leave = function () { SF.leave(secret.channel, teamId); };
/*
var uo = store.manager.addProxy(id, sf.rt, leave, secondaryKey);
// NOTE: Shared folder migration, disable for now
SF.checkMigration(secondaryKey, sf.rt.proxy, uo, function () {
cb(sf.rt);
});
*/
store.manager.addProxy(id, sf.rt, leave, secondaryKey);
cb(sf.rt);
});
sf.teams.push({
cb: cb,
store: store,
id: id
});
if (handler) { handler(id, sf.rt); }
return;
}
if (sf && !sf.ready && sf.rt) {
// The shared folder is loading, add our callbacks to the queue
sf.teams.push({
cb: cb,
store: store,
secondaryKey: secondaryKey,
id: id
});
if (handler) { handler(id, sf.rt); }
return;
}
sf = allSharedFolders[secret.channel] = {
teams: [{
cb: cb,
store: store,
secondaryKey: secondaryKey,
id: id
}],
readOnly: !Boolean(secondaryKey)
};
var owners = data.owners;
var listmapConfig = {
data: {},
channel: secret.channel,
readOnly: !Boolean(secondaryKey),
crypto: Crypto.createEncryptor(secret.keys),
userName: 'sharedFolder',
logLevel: 1,
ChainPad: ChainPad,
classic: true,
network: network,
Cache: Cache, // shared-folder cache
metadata: {
validateKey: secret.keys.validateKey || undefined,
owners: owners
},
onRejected: config.Store && config.Store.onRejected
};
var rt = sf.rt = Listmap.create(listmapConfig);
rt.proxy.on('cacheready', function () {
if (!sf.teams) {
return;
}
sf.teams.forEach(function (obj) {
var leave = function () { SF.leave(secret.channel, obj.store.id); };
// We can safely call addProxy and obj.cb here because
// 1. addProxy won't re-add the same folder twice on 'ready'
// 2. obj.cb is using Util.once
rt.cache = true;
// If we're updating the password of an existing folder, force the creation
// of a new userobject in proxy-manager. Once it's done, remove this flag
// to make sure we won't create a second new userobject on 'ready'
obj.store.manager.addProxy(obj.id, rt, leave, obj.secondaryKey, config.updatePassword);
config.updatePassword = false;
obj.cb(sf.rt);
});
sf.ready = true;
});
rt.proxy.on('ready', function () {
if (isNew && !Object.keys(rt.proxy).length) {
// New Shared folder: no migration required
rt.proxy.version = 2;
}
if (!sf.teams) {
return;
}
sf.teams.forEach(function (obj) {
var leave = function () { SF.leave(secret.channel, obj.store.id); };
/*
var uo = obj.store.manager.addProxy(obj.id, rt, leave, obj.secondaryKey);
// NOTE: Shared folder migration, disable for now
SF.checkMigration(secondaryKey, rt.proxy, uo, function () {
obj.cb(sf.rt);
});
*/
rt.cache = false;
obj.store.manager.addProxy(obj.id, rt, leave, obj.secondaryKey, config.updatePassword);
obj.cb(sf.rt);
});
sf.ready = true;
});
rt.proxy.on('error', function (info) {
if (info && info.error) {
if (info.error === "EDELETED" ) {
try {
// Deprecate the shared folder from each team
// We can only hide it
sf.teams.forEach(function (obj) {
obj.store.manager.deprecateProxy(obj.id, secret.channel, info.message);
if (obj.store.handleSharedFolder) {
obj.store.handleSharedFolder(obj.id, null);
}
obj.cb();
});
} catch (e) {}
delete allSharedFolders[secret.channel];
// This shouldn't be called on init because we're calling "isNewChannel" first,
// but we can still call "cb" just in case. This wait we make sure we won't block
// the initial "waitFor"
return void cb();
}
if (info.error === "ERESTRICTED" ) {
sf.teams.forEach(function (obj) {
obj.store.manager.restrictedProxy(obj.id, secret.channel);
obj.cb();
});
delete allSharedFolders[secret.channel];
return void cb();
}
}
});
if (handler) { handler(id, rt); }
});
};
SF.upgrade = function (channel, secret) {
var sf = allSharedFolders[channel];
if (!sf || !sf.readOnly) { return; }
if (!sf.rt.setReadOnly) { return; }
if (!secret.keys || !secret.keys.editKeyStr) { return; }
var crypto = Crypto.createEncryptor(secret.keys);
sf.readOnly = false;
sf.rt.setReadOnly(false, crypto);
};
SF.leave = function (channel, teamId) {
var sf = allSharedFolders[channel];
if (!sf) { return; }
var clients = sf.teams;
if (!Array.isArray(clients)) { return; }
// Remove the shared folder from the client's store and
// remove the client/team from our list
var idx;
clients.some(function (obj, i) {
if (obj.store.id === teamId) {
if (obj.store.handleSharedFolder) {
obj.store.handleSharedFolder(obj.id, null);
}
idx = i;
return true;
}
});
if (typeof (idx) === "undefined") { return; }
// Remove the selected team
clients.splice(idx, 1);
//If all the teams have closed this shared folder, stop it
if (clients.length) { return; }
if (sf.rt && sf.rt.stop) {
sf.rt.stop();
}
};
// Update the password locally
SF.updatePassword = function (Store, data, network, cb) {
var oldChannel = data.oldChannel;
var href = data.href;
var password = data.password;
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets(parsed.type, parsed.hash, password);
var sf = allSharedFolders[oldChannel];
if (!sf) { return void cb({ error: 'ENOTFOUND' }); }
if (sf.rt && sf.rt.stop) {
try { sf.rt.stop(); } catch (e) {}
}
var nt = nThen;
sf.teams.forEach(function (obj) {
nt = nt(function (waitFor) {
var s = obj.store;
var sfId = obj.id;
var shared = Util.find(s.proxy, ['drive', UserObject.SHARED_FOLDERS]) || {};
if (!sfId || !shared[sfId]) { return; }
var sf = JSON.parse(JSON.stringify(shared[sfId]));
sf.password = password;
SF.load({
network: network,
store: s,
updatePassword: true,
Store: Store,
isNewChannel: Store.isNewChannel
}, sfId, sf, waitFor());
if (!s.rpc) { return; }
s.rpc.unpin([oldChannel], waitFor());
s.rpc.pin([secret.channel], waitFor());
}).nThen;
});
nt(function () {
cb();
});
};
/* loadSharedFolders
load all shared folder stored in a given drive
- store: user or team main store
- userObject: userObject associated to the main drive
- handler: a function (sfid, rt) called for each shared folder loaded
*/
SF.loadSharedFolders = function (Store, network, store, drive, userObject, waitFor, progress, cache) {
var shared = drive[UserObject.SHARED_FOLDERS] || {};
var steps = Object.keys(shared).length;
var i = 1;
var w = waitFor();
progress = progress || function () {};
nThen(function (waitFor) {
Object.keys(shared).forEach(function (id) {
var sf = shared[id];
SF.load({
network: network,
store: store,
Store: Store,
cache: cache,
isNewChannel: Store.isNewChannel
}, id, sf, waitFor(function () {
progress({
progress: i,
max: steps
});
i++;
}));
});
}).nThen(function () {
setTimeout(w);
});
};
SF.isSharedFolderChannel = function (chanId) {
return Object.keys(allSharedFolders).includes(chanId);
};
return SF;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('../../common/common-hash'),
require('../../common/common-util'),
require('../../common/user-object'),
require('../../common/cache-store'),
require('nthen'),
require('chainpad-crypto'),
require('chainpad-listmap'),
require('chainpad')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-hash.js',
'/common/common-util.js',
'/common/user-object.js',
'/common/outer/cache-store.js',
'/components/nthen/index.js',
'/components/chainpad-crypto/crypto.js',
'chainpad-listmap',
'/components/chainpad/chainpad.dist.js',
], factory);
} else {
// unsupported initialization
}
})();

View File

@ -6,7 +6,7 @@ define([
'/file/file-crypto.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/outer/cache-store.js',
'/common/cache-store.js',
'/components/nthen/index.js',
], function (FileCrypto, Hash, Util, Cache, nThen) {
var module = {};

View File

@ -1,68 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (AppConfig = {}, ApiConfig = {},
OOCurrentVersion) => {
let availablePadTypes = [];
const OO_APPS = ["sheet", "doc", "presentation"];
const setCustomize = data => {
AppConfig = data.AppConfig;
ApiConfig = data.ApiConfig;
const ooEnabled = ApiConfig.onlyOffice &&
ApiConfig.onlyOffice.availableVersions.includes(
OOCurrentVersion.currentVersion
);
availablePadTypes = AppConfig.availablePadTypes.filter(
(t) => ooEnabled || !OO_APPS.includes(t)
);
};
// Initialize values when using in browser directly
if (Object.keys(AppConfig).length) {
setCustomize({AppConfig,ApiConfig});
}
const Types = { setCustomize };
Types.__defineGetter__("availableTypes", function () {
if (ApiConfig.appsToDisable) {
return availablePadTypes.filter(value => {
return !ApiConfig.appsToDisable.includes(value);
});
}
return availablePadTypes;
});
Types.__defineGetter__("appsToSelect", function () {
return availablePadTypes.filter(value => !['drive', 'teams', 'file', 'contacts', 'convert'].includes(value));
});
Types.isAvailable = type => {
return Array.isArray(Types.availableTypes) &&
Types.availableTypes.includes(type);
};
return Types;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
undefined,
undefined,
require('./onlyoffice/current-version')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/customize/application_config.js',
"/api/config",
"/common/onlyoffice/current-version.js",
], factory);
} else {
// unsupported initialization
}
})();

View File

@ -1,235 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(function () {
var factory = function (Util, Rpc) {
var create = function (network, proxy, _cb, Cache) {
if (typeof(_cb) !== 'function') { throw new Error("Expected callback"); }
var cb = Util.once(Util.mkAsync(_cb));
if (!network) { return void cb('INVALID_NETWORK'); }
if (!proxy) { return void cb('INVALID_PROXY'); }
var edPrivate = proxy.edPrivate;
var edPublic = proxy.edPublic;
if (!(edPrivate && edPublic)) { return void cb('INVALID_KEYS'); }
Rpc.create(network, edPrivate, edPublic, function (e, rpc) {
if (e) { return void cb(e); }
var exp = {};
exp.destroy = rpc.destroy;
// expose the supplied publicKey as an identifier
exp.publicKey = edPublic;
// expose the RPC module's raw 'send' command
exp.send = rpc.send;
// you can ask the server to pin a particular channel for you
exp.pin = function (channels, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!Array.isArray(channels)) {
return void cb('[TypeError] pin expects an array');
}
rpc.send('PIN', channels, cb);
};
// you can also ask to unpin a particular channel
exp.unpin = function (channels, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!Array.isArray(channels)) {
return void cb('[TypeError] pin expects an array');
}
rpc.send('UNPIN', channels, cb);
};
// Get data for the admin panel
exp.adminRpc = function (obj, cb) {
if (!obj.cmd) {
setTimeout(function () {
cb('[TypeError] admin rpc expects a command');
});
return;
}
var params = [obj.cmd, obj.data];
rpc.send('ADMIN', params, cb);
};
// ask the server what it thinks your hash is
exp.getServerHash = function (cb) {
rpc.send('GET_HASH', edPublic, function (e, hash) {
if (!(hash && hash[0])) {
return void cb('NO_HASH_RETURNED');
}
cb(e, Array.isArray(hash) && hash[0] || undefined);
});
};
// if local and remote hashes don't match, send a reset
exp.reset = function (channels, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!Array.isArray(channels)) {
return void cb('[TypeError] pin expects an array');
}
rpc.send('RESET', channels, cb);
};
// get the combined size of all channels (in bytes) for all the
// channels which the server has pinned for your publicKey
exp.getFileListSize = function (cb) {
rpc.send('GET_TOTAL_SIZE', undefined, function (e, response) {
if (e) { return void cb(e); }
if (response && response.length && typeof(response[0]) === 'number') {
cb(void 0, response[0]);
} else {
cb('INVALID_RESPONSE');
}
});
};
// Update the limit value for all the users and return the limit for your publicKey
exp.updatePinLimits = function (cb) {
rpc.send('UPDATE_LIMITS', undefined, function (e, response) {
if (e) { return void cb(e); }
if (response && response.length && typeof(response[0]) === "number") {
cb (void 0, response[0], response[1], response[2]);
} else {
cb('INVALID_RESPONSE');
}
});
};
// Get the storage limit associated with your publicKey
exp.getLimit = function (cb) {
rpc.send('GET_LIMIT', undefined, function (e, response) {
if (e) { return void cb(e); }
if (response && response.length && typeof(response[0]) === "number") {
cb (void 0, response[0], response[1], response[2]);
} else {
cb('INVALID_RESPONSE');
}
});
};
exp.trimHistory = function (data, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (typeof(data) !== 'object' || !data.channel || !data.hash) {
return void cb('INVALID_ARGUMENTS');
}
rpc.send('TRIM_HISTORY', data, function (e) {
if (e) { return cb(e); }
cb();
});
};
exp.clearOwnedChannel = function (channel, cb) {
if (typeof(channel) !== 'string' || channel.length !== 32) {
return void cb('INVALID_ARGUMENTS');
}
rpc.send('CLEAR_OWNED_CHANNEL', channel, function (e) {
if (e) { return cb(e); }
cb();
});
};
exp.removeOwnedChannel = function (channel, cb, reason) {
if (typeof(channel) !== 'string' || [32,48].indexOf(channel.length) === -1) {
console.error('invalid channel to remove', channel);
return void cb('INVALID_ARGUMENTS');
}
rpc.send('REMOVE_OWNED_CHANNEL', {
channel: channel,
reason: reason
}, function (e, response) {
if (e) { return void cb(e); }
if (response && response.length && response[0] === "OK") {
cb();
if (Cache && Cache.clearChannel) {
Cache.clearChannel(channel);
}
} else {
cb('INVALID_RESPONSE');
}
});
};
exp.removePins = function (cb) {
rpc.send('REMOVE_PINS', undefined, function (e, response) {
if (e) { return void cb(e); }
if (response && response.length && response[0] === "OK") {
cb();
} else {
cb('INVALID_RESPONSE');
}
});
};
exp.uploadComplete = function (id, cb) {
rpc.send('UPLOAD_COMPLETE', id, function (e, res) {
if (e) { return void cb(e); }
var id = res[0];
if (typeof(id) !== 'string') {
return void cb('INVALID_ID');
}
cb(void 0, id);
});
};
exp.ownedUploadComplete = function (id, cb) {
rpc.send('OWNED_UPLOAD_COMPLETE', id, function (e, res) {
if (e) { return void cb(e); }
var id = res[0];
if (typeof(id) !== 'string') {
return void cb('INVALID_ID');
}
cb(void 0, id);
});
};
exp.uploadStatus = function (size, cb) {
if (typeof(size) !== 'number') {
return void setTimeout(function () {
cb('INVALID_SIZE');
});
}
rpc.send('UPLOAD_STATUS', size, function (e, res) {
if (e) { return void cb(e); }
var pending = res[0];
if (typeof(pending) !== 'boolean') {
return void cb('INVALID_RESPONSE');
}
cb(void 0, pending);
});
};
exp.uploadCancel = function (size, cb) {
rpc.send('UPLOAD_CANCEL', size, function (e) {
if (e) { return void cb(e); }
cb();
});
};
// Get data for the admin panel
exp.setMetadata = function (obj, cb) {
rpc.send('SET_METADATA', {
channel: obj.channel,
command: obj.command,
value: obj.value
}, cb);
};
cb(e, exp);
});
};
return { create: create };
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(require('./common-util'), require("./rpc"));
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([ '/common/common-util.js', '/common/rpc.js', ], function (Util, Rpc) { return factory(Util, Rpc); });
}
}());

File diff suppressed because it is too large Load Diff

View File

@ -1,422 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(function () {
var factory = function (Util, Nacl) {
// we will send messages with a unique id for each RPC
// that id is returned with each response, indicating which call it was in response to
var uid = Util.uid;
// safely parse json messages, because they might cause parse errors
var tryParse = Util.tryParse;
// we will sign various message with our edPrivate keys
// this handles that in a generic way
var signMsg = function (data, signKey) {
var buffer = Util.decodeUTF8(JSON.stringify(data));
return Util.encodeBase64(Nacl.sign.detached(buffer, signKey));
};
// sendMsg takes a pre-formed message, does a little validation
// adds a transaction id to the message and stores its callback
// and finally sends it off to the historyKeeper, which delegates its
// processing to the RPC submodule
var sendMsg = function (ctx, data, cb) {
if (typeof(cb) !== 'function') { throw new Error('expected callback'); }
var network = ctx.network;
var hkn = network.historyKeeper;
if (typeof(hkn) !== 'string') { return void cb("NO_HISTORY_KEEPER"); }
var txid = uid();
var pending = ctx.pending[txid] = function (err, response) {
cb(err, response);
};
pending.data = data;
pending.called = 0;
return network.sendto(hkn, JSON.stringify([txid, data]));
};
var matchesAnon = function (ctx, txid) {
if (!ctx.anon) { return false; }
if (typeof(ctx.anon.pending[txid]) !== 'function') { return false; }
return true;
};
var handleAnon = function (ctx /* anon_ctx */, txid, body /* parsed messages without txid */) {
// if anon is handling it we know there's a pending callback
var pending = ctx.pending[txid];
if (body[0] === 'ERROR') { pending(body[1]); }
else { pending(void 0, body.slice(1)); }
delete ctx.pending[txid];
};
var onMsg = function (ctx /* network context */, msg /* string message */) {
if (typeof(msg) !== 'string') {
console.error("received non-string message [%s]", msg);
}
var parsed = tryParse(msg);
if (!parsed) {
return void console.error(new Error('could not parse message: %s', msg));
}
// RPC messages are always arrays.
if (!Array.isArray(parsed)) { return; }
// ignore FULL_HISTORY messages
if (/(FULL_HISTORY|HISTORY_RANGE)/.test(parsed[0])) { return; }
var txid = parsed[0];
// txid must be a string, or this message is not meant for us
if (typeof(txid) !== 'string') { return; }
if (matchesAnon(ctx, txid)) {
return void handleAnon(ctx.anon, txid, parsed.slice(1));
}
// iterate over authenticated rpc contexts and check if they are expecting
// a message with this txid
if (ctx.authenticated.some(function (rpc_ctx) {
var pending = rpc_ctx.pending[txid];
// not meant for you
if (typeof(pending) !== 'function') { return false; }
// if you're here, the message is for you...
if (parsed[1] !== 'ERROR') {
// if the server sent you a new cookie, replace the old one
if (/\|/.test(parsed[1]) && rpc_ctx.cookie !== parsed[1]) {
rpc_ctx.cookie = parsed[1];
}
pending(void 0, parsed.slice(2));
// if successful, delete the callback...
delete rpc_ctx.pending[txid];
// prevent further iteration
return true;
}
// NO_COOKIE errors mean you failed to authenticate.
// request a new cookie and resend the query
if (parsed[2] === 'NO_COOKIE') {
rpc_ctx.send('COOKIE', "", function (e) {
if (e) {
console.error(e);
return void pending(e);
}
// resend the same command again
// give up if you've already tried resending
if (rpc_ctx.resend(txid)) { delete rpc_ctx.pending[txid]; }
});
// prevent further iteration
return true;
}
// if you're here then your RPC passed authentication but had some other error
// call back with the error message
pending(parsed[2]);
// and delete the pending callback
delete rpc_ctx.pending[txid];
// prevent further iteration
return true;
})) {
// the message was handled, so stop here
return;
}
console.error("UNHANDLED RPC MESSAGE", msg);
};
var networks = [];
var contexts = [];
var initNetworkContext = function (network) {
var ctx = {
network: network,
connected: true,
anon: undefined,
authenticated: [],
};
networks.push(network);
contexts.push(ctx);
// add listeners...
network.on('message', function (msg, sender) {
if (sender !== network.historyKeeper) { return; }
onMsg(ctx, msg);
});
network.on('disconnect', function () {
ctx.connected = false;
if (ctx.anon) { ctx.anon.connected = false; }
ctx.authenticated.forEach(function (ctx) {
ctx.connected = false;
});
});
network.on('reconnect', function () {
if (ctx.anon) { ctx.anon.connected = true; }
ctx.authenticated.forEach(function (ctx) {
ctx.connected = true;
});
});
return ctx;
};
var getNetworkContext = function (network) {
var i;
networks.some(function (current, j) {
if (network !== current) { return false; }
i = j;
return true;
});
if (contexts[i]) { return contexts[i]; }
return initNetworkContext(network);
};
var initAuthenticatedRpc = function (networkContext, keys) {
var ctx = {
network: networkContext.network,
publicKey: keys.publicKeyString,
timeouts: {},
pending: {},
cookie: null,
connected: true,
};
var send = ctx.send = function (type, msg, _cb) {
var cb = Util.mkAsync(_cb);
if (!ctx.connected && type !== 'COOKIE') {
return void cb("DISCONNECTED");
}
// construct a signed message...
var data = [type, msg];
if (ctx.cookie && ctx.cookie.join) {
data.unshift(ctx.cookie.join('|'));
} else {
data.unshift(ctx.cookie);
}
var sig = signMsg(data, keys.signKey);
data.unshift(keys.publicKeyString);
data.unshift(sig);
// [sig, edPublicKey, cookie, type, msg]
return sendMsg(ctx, data, cb);
};
ctx.resend = function (txid) {
var pending = ctx.pending[txid];
if (pending.called) {
console.error("[%s] called too many times", txid);
return true;
}
pending.called++;
// update the cookie and signature...
pending.data[2] = ctx.cookie;
pending.data[0] = signMsg(pending.data.slice(2), keys.signKey);
// store the callback with a new txid
var new_txid = uid();
ctx.pending[new_txid] = pending;
// and delete the old one
delete ctx.pending[txid];
try {
return ctx.network.sendto(ctx.network.historyKeeper,
JSON.stringify([new_txid, pending.data]));
} catch (e) {
console.log("failed to resend");
console.error(e);
}
};
send.unauthenticated = function (type, msg, _cb) {
var cb = Util.mkAsync(_cb);
if (!ctx.connected) { return void cb('DISCONNECTED'); }
// construct an unsigned message
var data = [null, keys.publicKeyString, null, type, msg];
if (ctx.cookie && ctx.cookie.join) {
data[2] = ctx.cookie.join('|');
} else {
data[2] = ctx.cookie;
}
return sendMsg(ctx, data, cb);
};
ctx.destroy = function () {
// clear all pending timeouts
Object.keys(ctx.timeouts).forEach(function (to) {
clearTimeout(to);
});
// remove the ctx from the network's stack
var idx = networkContext.authenticated.indexOf(ctx);
if (idx === -1) { return; }
networkContext.authenticated.splice(idx, 1);
};
networkContext.authenticated.push(ctx);
return ctx;
};
var getAuthenticatedContext = function (networkContext, keys) {
if (!networkContext) { throw new Error('expected network context'); }
var publicKey = keys.publicKeyString;
var i;
networkContext.authenticated.some(function (ctx, j) {
if (ctx.publicKey !== publicKey) { return false; }
i = j;
return true;
});
if (networkContext.authenticated[i]) { return networkContext.authenticated[i]; }
return initAuthenticatedRpc(networkContext, keys);
};
var create = function (network, edPrivateKey, edPublicKey, _cb) {
if (typeof(_cb) !== 'function') { throw new Error("expected callback"); }
var cb = Util.mkAsync(_cb);
var signKey;
try {
signKey = Util.decodeBase64(edPrivateKey);
if (signKey.length !== 64) {
throw new Error('private key did not match expected length of 64');
}
} catch (err) {
return void cb(err);
}
try {
if (Util.decodeBase64(edPublicKey).length !== 32) {
return void cb('expected public key to be 32 uint');
}
} catch (err) { return void cb(err); }
if (!network) { return void cb('NO_NETWORK'); }
// get or create a context for the provided network
var net_ctx = getNetworkContext(network);
var rpc_ctx = getAuthenticatedContext(net_ctx, {
publicKeyString: edPublicKey,
signKey: signKey,
});
rpc_ctx.send('COOKIE', "", function (e) {
if (e) { return void cb(e); }
// callback to provide 'send' method to whatever needs it
cb(void 0, {
send: rpc_ctx.send,
destroy: rpc_ctx.destroy,
});
});
};
var initAnonRpc = function (networkContext) {
var ctx = {
network: networkContext.network,
timeouts: {},
pending: {},
connected: true,
};
// any particular network will only ever need one anonymous rpc
networkContext.anon = ctx;
ctx.send = function (type, msg, _cb) {
var cb = Util.mkAsync(_cb);
if (!ctx.connected) { return void cb('DISCONNECTED'); }
// construct an unsigned message...
var data = [type, msg];
// [type, msg]
return sendMsg(ctx, data, cb);
};
ctx.resend = function (txid) {
var pending = ctx.pending[txid];
if (pending.called) {
console.error("[%s] called too many times", txid);
return true;
}
pending.called++;
try {
return ctx.network.sendto(ctx.network.historyKeeper,
JSON.stringify([txid, pending.data]));
} catch (e) {
console.log("failed to resend");
console.error(e);
}
};
ctx.destroy = function () {
// clear all pending timeouts
Object.keys(ctx.timeouts).forEach(function (to) {
clearTimeout(to);
});
networkContext.anon = undefined;
};
return ctx;
};
var getAnonContext = function (networkContext) {
return networkContext.anon || initAnonRpc(networkContext);
};
var createAnonymous = function (network, _cb) {
// enforce asynchrony
var cb = Util.mkAsync(_cb);
if (typeof(cb) !== 'function') { throw new Error("expected callback"); }
if (!network) { return void cb('NO_NETWORK'); }
// get or create a context for the provided network
var ctx = getAnonContext(getNetworkContext(network));
cb(void 0, {
send: ctx.send,
destroy: ctx.destroy,
});
};
return { create: create, createAnonymous: createAnonymous };
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(require("./common-util"), require("tweetnacl/nacl-fast"));
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/components/tweetnacl/nacl-fast.min.js',
], function (Util) {
return factory(Util, window.nacl);
});
} else {
// I'm not gonna bother supporting any other kind of instanciation
}
}());

View File

@ -138,7 +138,7 @@ define([
'/common/cryptpad-common.js',
'/components/chainpad-crypto/crypto.js',
'/common/cryptget.js',
'/common/outer/worker-channel.js',
'/common/events-channel.js',
'/secureiframe/main.js',
'/unsafeiframe/main.js',
'/common/onlyoffice/ooiframe.js',
@ -151,7 +151,7 @@ define([
'/common/common-feedback.js',
'/common/outer/local-store.js',
'/common/outer/login-block.js',
'/common/outer/cache-store.js',
'/common/cache-store.js',
'/customize/application_config.js',
//'/common/test.js',
'/common/user-object.js',

View File

@ -8,7 +8,7 @@ define([
'/components/nthen/index.js',
'/customize/messages.js',
'/common/sframe-chainpad-netflux-inner.js',
'/common/outer/worker-channel.js',
'/common/events-channel.js',
'/common/sframe-common-title.js',
'/common/common-ui-elements.js',
'/common/sframe-common-history.js',

View File

@ -1,999 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Util, Hash, Realtime) => {
let window = globalThis;
var module = {};
module.setCustomize = (/*data*/) => {
};
var clone = function (o) {
try { return JSON.parse(JSON.stringify(o)); }
catch (e) { return undefined; }
};
module.init = function (config, exp, files) {
var loggedIn = config.loggedIn;
var sharedFolder = config.sharedFolder;
var readOnly = config.readOnly;
var Messages = config.Messages || {};
var ROOT = exp.ROOT;
var FILES_DATA = exp.FILES_DATA;
var STATIC_DATA = exp.STATIC_DATA;
var OLD_FILES_DATA = exp.OLD_FILES_DATA;
var UNSORTED = exp.UNSORTED;
var TRASH = exp.TRASH;
var TEMPLATE = exp.TEMPLATE;
var SHARED_FOLDERS = exp.SHARED_FOLDERS;
var SHARED_FOLDERS_TEMP = exp.SHARED_FOLDERS_TEMP;
var debug = exp.debug;
exp._setReadOnly = function (state) {
readOnly = state;
if (!readOnly) { exp.fixFiles(); }
};
exp.setHref = function (channel, id, href) {
if (!id && !channel) { return; }
if (readOnly) { return; }
var ids = id ? [id] : exp.findChannels([channel]);
ids.forEach(function (i) {
var data = exp.getFileData(i, true);
var oldHref = exp.getHref(data);
if (oldHref === href) { return; }
data.href = exp.cryptor.encrypt(href);
});
};
exp.setPadAttribute = function (href, attr, value, cb) {
cb = cb || function () {};
if (readOnly) { return void cb('EFORBIDDEN'); }
var id = exp.getIdFromHref(href);
if (!id) { return void cb("E_INVAL_HREF"); }
if (!attr || !attr.trim()) { return void cb("E_INVAL_ATTR"); }
var data = exp.getFileData(id, true);
if (attr === "href") {
exp.setHref(null, id, value);
} else {
data[attr] = clone(value);
}
cb(null);
};
exp.getPadAttribute = function (href, attr, cb) {
cb = cb || function () {};
var id = exp.getIdFromHref(href);
if (!id) { return void cb(null, undefined); }
var data = exp.getFileData(id);
cb(null, clone(data[attr]));
};
exp.pushData = function (_data, cb) {
if (typeof cb !== "function") { cb = function () {}; }
if (readOnly) { return void cb('EFORBIDDEN'); }
var id = Util.createRandomInteger();
var data = clone(_data);
// If we were given an edit link, encrypt its value if needed
if (data.href && data.href.indexOf('#') !== -1) { data.href = exp.cryptor.encrypt(data.href); }
files[FILES_DATA][id] = data;
cb(null, id);
};
exp.pushLink = function (_data, cb) {
if (typeof cb !== "function") { cb = function () {}; }
if (readOnly) { return void cb('EFORBIDDEN'); }
var id = Util.createRandomInteger();
var data = clone(_data);
files[STATIC_DATA][id] = data;
cb(null, id);
};
exp.pushSharedFolder = function (_data, cb) {
if (typeof cb !== "function") { cb = function () {}; }
if (readOnly) { return void cb('EFORBIDDEN'); }
var data = clone(_data);
// Check if we already have this shared folder in our drive
var exists;
if (Object.keys(files[SHARED_FOLDERS]).some(function (k) {
if (files[SHARED_FOLDERS][k].channel === data.channel) {
// We already know this shared folder. Check if we can get better access rights
if (data.href && !files[SHARED_FOLDERS][k].href) {
files[SHARED_FOLDERS][k].href = data.href;
}
exists = k;
return true;
}
})) {
return void cb ('EEXISTS', exists);
}
// Add the folder
if (!loggedIn || config.testMode) {
return void cb("EAUTH");
}
var id = Util.createRandomInteger();
if (data.href && data.href.indexOf('#') !== -1) { data.href = exp.cryptor.encrypt(data.href); }
files[SHARED_FOLDERS][id] = data;
cb(null, id);
};
exp.deprecateSharedFolder = function (id, reason) {
if (readOnly) { return; }
var data = files[SHARED_FOLDERS][id];
if (!data) { return; }
var ro = !data.href || exp.cryptor.decrypt(data.href).indexOf('#') === -1;
if (!ro) {
var obj = files[SHARED_FOLDERS_TEMP][id] = JSON.parse(JSON.stringify(data));
obj.legacy = reason !== "PASSWORD_CHANGE";
}
var paths = exp.findFile(Number(id));
exp.delete(paths, null, true);
delete files[SHARED_FOLDERS][id];
};
// FILES DATA
var spliceFileData = function (id) {
if (readOnly) { return; }
delete files[FILES_DATA][id];
};
// Find files in FILES_DATA that are not anymore in the drive, and remove them from
// FILES_DATA.
exp.checkDeletedFiles = function (cb) {
if (!loggedIn && !config.testMode) { return void cb(); }
if (readOnly) { return void cb('EFORBIDDEN'); }
var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
var toClean = [];
exp.getFiles([FILES_DATA, SHARED_FOLDERS, STATIC_DATA]).forEach(function (id) {
if (filesList.indexOf(id) === -1) {
var fd = exp.isSharedFolder(id) ? files[SHARED_FOLDERS][id] : exp.getFileData(id);
var channelId = fd.channel;
if (fd.lastVersion) { toClean.push(Hash.hrefToHexChannelId(fd.lastVersion)); }
if (fd.rtChannel) { toClean.push(fd.rtChannel); }
if (channelId) { toClean.push(channelId); }
if (exp.isSharedFolder(id)) {
delete files[SHARED_FOLDERS][id];
if (config.removeProxy) { config.removeProxy(id); }
} else if (files[STATIC_DATA][id]) {
delete files[STATIC_DATA][id];
} else {
spliceFileData(id);
}
}
});
if (!toClean.length) { return void cb(); }
cb(null, toClean);
};
var deleteHrefs = function (ids) {
if (readOnly) { return; }
ids.forEach(function (obj) {
var idx = files[obj.root].indexOf(obj.id);
files[obj.root].splice(idx, 1);
});
};
var deleteMultipleTrashRoot = function (roots) {
if (readOnly) { return; }
roots.forEach(function (obj) {
var idx = files[TRASH][obj.name].indexOf(obj.el);
files[TRASH][obj.name].splice(idx, 1);
});
};
exp.deleteMultiplePermanently = function (paths, nocheck, cb) {
if (readOnly) { return void cb('EFORBIDDEN'); }
var allFilesPaths = paths.filter(function(x) { return exp.isPathIn(x, [FILES_DATA]); });
if (!loggedIn && !config.testMode) {
allFilesPaths.forEach(function (path) {
var id = path[1];
if (!id) { return; }
spliceFileData(id);
});
return void cb();
}
var hrefPaths = paths.filter(function(x) { return exp.isPathIn(x, ['hrefArray']); });
var rootPaths = paths.filter(function(x) { return exp.isPathIn(x, [ROOT]); });
var trashPaths = paths.filter(function(x) { return exp.isPathIn(x, [TRASH]); });
var ids = [];
hrefPaths.forEach(function (path) {
var id = exp.find(path);
ids.push({
root: path[0],
id: id
});
});
deleteHrefs(ids);
rootPaths.forEach(function (path) {
var parentPath = path.slice();
var key = parentPath.pop();
var parentEl = exp.find(parentPath);
delete parentEl[key];
});
var trashRoot = [];
trashPaths.forEach(function (path) {
var parentPath = path.slice();
var key = parentPath.pop();
var parentEl = exp.find(parentPath);
// Trash root: we have array here, we can't just splice with the path otherwise we might break the path
// of another element in the loop
if (path.length === 4) {
trashRoot.push({
name: path[1],
el: parentEl
});
return;
}
// Trash but not root: it's just a tree so remove the key
delete parentEl[key];
});
deleteMultipleTrashRoot(trashRoot);
// In some cases, we want to remove pads from a location without removing them from
// FILES_DATA (replaceHref)
if (!nocheck) { exp.checkDeletedFiles(cb); }
else { cb(); }
};
// Move
// From another drive
exp.copyFromOtherDrive = function (path, element, data, key) {
if (readOnly) { return; }
// Copy files data
// We have to remove pads that are already in the current proxy to make sure
// we won't create duplicates
var toRemove = [];
Object.keys(data).forEach(function (id) {
id = Number(id);
// Find and maybe update existing pads with the same channel id
var d = data[id];
// If we were given a static link, copy to STATIC_DATA
if (d.static) {
delete d.static;
files[STATIC_DATA][id] = d;
return;
}
// If we were given an edit link, encrypt its value if needed
if (d.href) { d.href = exp.cryptor.encrypt(d.href); }
var found = false;
for (var i in files[FILES_DATA]) {
if (files[FILES_DATA][i].channel === d.channel) {
// Update href?
if (!files[FILES_DATA][i].href) {
files[FILES_DATA][i].href = d.href;
}
found = true;
break;
}
}
if (found) {
toRemove.push(id);
return;
}
files[FILES_DATA][id] = d;
});
// Remove existing pads from the "element" variable
if (exp.isFile(element) && toRemove.indexOf(element) !== -1) {
exp.log(Messages.sharedFolders_duplicate);
return;
} else if (exp.isFolder(element)) {
var _removeExisting = function (root) {
for (var k in root) {
if (exp.isFile(root[k])) {
if (toRemove.indexOf(root[k]) !== -1) {
exp.log(Messages.sharedFolders_duplicate);
delete root[k];
}
} else if (exp.isFolder(root[k])) {
_removeExisting(root[k]);
}
}
};
_removeExisting(element);
}
// Copy file or folder
var newParent = exp.find(path);
var tempName = exp.isFile(element) ? Hash.createChannelId() : key;
var newName = exp.getAvailableName(newParent, tempName);
if (Array.isArray(newParent)) {
newParent.push(element);
return;
}
newParent[newName] = element;
};
// From the same drive
var pushToTrash = function (name, element, path) {
if (readOnly) { return; }
var trash = files[TRASH];
if (typeof(trash[name]) === "undefined") { trash[name] = []; }
var trashArray = trash[name];
var trashElement = {
element: element,
path: path
};
trashArray.push(trashElement);
};
exp.copyElement = function (elementPath, newParentPath) {
if (readOnly) { return; }
if (exp.comparePath(elementPath, newParentPath)) { return; } // Nothing to do...
var element = exp.find(elementPath);
var newParent = exp.find(newParentPath);
// Move to Trash
if (exp.isPathIn(newParentPath, [TRASH])) {
if (!elementPath || elementPath.length < 2 || elementPath[0] === TRASH) {
debug("Can't move an element from the trash to the trash: ", elementPath);
return;
}
var key = elementPath[elementPath.length - 1];
var elName = exp.isPathIn(elementPath, ['hrefArray']) ? exp.getTitle(element) : key;
var parentPath = elementPath.slice();
parentPath.pop();
pushToTrash(elName, element, parentPath);
return true;
}
// Move to hrefArray
if (exp.isPathIn(newParentPath, ['hrefArray'])) {
if (exp.isFolder(element)) {
exp.log(Messages.fo_moveUnsortedError);
return;
} else {
if (elementPath[0] === newParentPath[0]) { return; }
var fileRoot = newParentPath[0];
if (files[fileRoot].indexOf(element) === -1) {
files[fileRoot].push(element);
}
return true;
}
}
// Move to root
var newName = exp.isFile(element) ?
exp.getAvailableName(newParent, Hash.createChannelId()) :
exp.isInTrashRoot(elementPath) ?
elementPath[1] : elementPath.pop();
if (typeof(newParent[newName]) !== "undefined") {
exp.log(Messages.fo_unavailableName);
return;
}
newParent[newName] = element;
return true;
};
// FORGET (move with href not path)
exp.forget = function (href) {
if (readOnly) { return; }
var id = exp.getIdFromHref(href);
if (!id) { return; }
if (!loggedIn && !config.testMode) {
// delete permanently
spliceFileData(id);
return true;
}
var paths = exp.findFile(id);
exp.move(paths, [TRASH]);
return true;
};
// REPLACE
// If all the occurences of an href are in the trash, remove them and add the file in root.
// This is use with setPadTitle when we open a stronger version of a deleted pad
exp.restoreHref = function (href) {
if (readOnly) { return; }
var idO = exp.getIdFromHref(href);
if (!idO || !exp.isFile(idO)) { return; }
var paths = exp.findFile(idO);
// Remove all the occurences in the trash
// If all the occurences are in the trash or no occurence, add the pad to root
var allInTrash = true;
paths.forEach(function (p) {
if (p[0] === TRASH) {
exp.delete(p, null, true); // 3rd parameter means skip "checkDeletedFiles"
return;
}
allInTrash = false;
});
if (allInTrash) {
exp.add(idO);
}
};
exp.add = function (id, path) {
if (readOnly) { return; }
if (!loggedIn && !config.testMode) { return; }
id = Number(id);
var data = files[FILES_DATA][id] || files[STATIC_DATA][id] || files[SHARED_FOLDERS][id];
if (!data || typeof(data) !== "object") { return; }
var newPath = path, parentEl;
if (path && !Array.isArray(path)) {
newPath = decodeURIComponent(path).split(',');
}
// Add to href array
if (path && exp.isPathIn(newPath, ['hrefArray'])) {
parentEl = exp.find(newPath);
parentEl.push(id);
return;
}
// Add to root if no path
var filesList = exp.getFiles([ROOT, TRASH, 'hrefArray']);
if (filesList.indexOf(id) === -1 && !newPath) {
newPath = [ROOT];
}
// Add to root
if (path && exp.isPathIn(newPath, [ROOT])) {
parentEl = exp.find(newPath);
if (parentEl) {
var newName = exp.getAvailableName(parentEl, Hash.createChannelId());
parentEl[newName] = id;
return;
} else {
parentEl = exp.find([ROOT]);
newPath.slice(1).forEach(function (folderName) {
parentEl = parentEl[folderName] = parentEl[folderName] || {};
});
parentEl[Hash.createChannelId()] = id;
}
}
};
exp.setFolderData = function (path, key, value, cb) {
if (readOnly) { return; }
var folder = exp.find(path);
if (!exp.isFolder(folder) || exp.isSharedFolder(folder)) { return; }
if (!exp.hasFolderData(folder)) {
var hashKey = "000" + Hash.createChannelId().slice(0, -3);
folder[hashKey] = {
metadata: true
};
}
exp.getFolderData(folder)[key] = value;
cb();
};
/**
* INTEGRITY CHECK
*/
var onSync = function (next) {
if (exp.rt) {
exp.rt.sync();
Realtime.whenRealtimeSyncs(exp.rt, next);
} else {
window.setTimeout(next, 1000);
}
};
exp.migrateReadOnly = function (cb) {
if (readOnly || !config.editKey) { return void cb({error: 'EFORBIDDEN'}); }
if (files.version >= 2) { return void cb(); } // Already migrated, nothing to do
files.migrateRo = 1;
var next = function () {
var copy = JSON.parse(JSON.stringify(files));
exp.reencrypt(config.editKey, config.editKey, copy);
setTimeout(function () {
if (files.version >= 2) {
// Already migrated by another user while we were re-encrypting
return void cb();
}
Object.keys(copy).forEach(function (k) {
files[k] = copy[k];
});
files.version = 2;
delete files.migrateRo;
onSync(cb);
}, 1000);
};
onSync(next);
};
exp.migrate = function (cb) {
if (readOnly) { return void cb(); }
// Make sure unsorted doesn't exist anymore
// Note: Unsorted only works with the old structure where pads are href
// It should be called before the migration code
var fixUnsorted = function () {
if (!files[UNSORTED] || !files[OLD_FILES_DATA]) { return; }
debug("UNSORTED still exists in the object, removing it...");
var us = files[UNSORTED];
if (us.length === 0) {
delete files[UNSORTED];
return;
}
us.forEach(function (el) {
if (typeof el !== "string") {
return;
}
var data = files[OLD_FILES_DATA].filter(function (x) {
return x.href === el;
});
if (data.length === 0) {
files[OLD_FILES_DATA].push({
href: el
});
}
return;
});
delete files[UNSORTED];
};
// mergeDrive...
var migrateToNewFormat = function (todo) {
if (!files[OLD_FILES_DATA]) {
return void todo();
}
try {
debug("Migrating file system...");
files.migrate = 1;
var next = function () {
var oldData = files[OLD_FILES_DATA].slice();
if (!files[FILES_DATA]) {
files[FILES_DATA] = {};
}
var newData = files[FILES_DATA];
//var oldFiles = oldData.map(function (o) { return o.href; });
oldData.forEach(function (obj) {
if (!obj || !obj.href) { return; }
var href = obj.href;
var id = Util.createRandomInteger();
var paths = exp.findFile(href);
var data = obj;
var key = Hash.createChannelId();
if (data) {
newData[id] = data;
} else {
newData[id] = {href: href};
}
paths.forEach(function (p) {
var parentPath = p.slice();
var okey = parentPath.pop(); // get the parent
var parent = exp.find(parentPath);
if (exp.isInTrashRoot(p)) {
parent.element = id;
newData[id].filename = p[1];
return;
}
if (exp.isPathIn(p, ['hrefArray'])) {
parent[okey] = id;
return;
}
// else root or trash (not trashroot)
parent[key] = id;
newData[id].filename = okey;
delete parent[okey];
});
});
delete files[OLD_FILES_DATA];
delete files.migrate;
todo();
};
onSync(next);
} catch(e) {
console.error(e);
todo();
}
};
fixUnsorted();
migrateToNewFormat(cb);
};
exp.fixFiles = function (silent) {
// Explore the tree and check that everything is correct:
// * 'root', 'trash', 'unsorted' and 'filesData' exist and are objects
// * ROOT: Folders are objects, files are href
// * TRASH: Trash root contains only arrays, each element of the array is an object {element:.., path:..}
// * OLD_FILES_DATA: - Data (title, cdate, adte) are stored in filesData. filesData contains only href keys linking to object with title, cdate, adate.
// - Dates (adate, cdate) can be parsed/formatted
// - All files in filesData should be either in 'root', 'trash' or 'unsorted'. If that's not the case, copy the fily to 'unsorted'
// * TEMPLATE: Contains only files (href), and does not contains files that are in ROOT
// We can't fix anything in read-only mode: abort
if (readOnly) { return; }
if (silent) { debug = function () {}; }
var t0 = +new Date();
debug("Cleaning file system...");
var before = JSON.stringify(files);
var fixRoot = function (elem) {
if (typeof(files[ROOT]) !== "object") { debug("ROOT was not an object"); files[ROOT] = {}; }
var element = elem || files[ROOT];
if (!element) { return console.error("Invalid element in root"); }
var nbMetadataFolders = 0;
// caching this variables saves a lot of hashmap lookups in this loop
var static_data = files[STATIC_DATA];
var files_data = files[FILES_DATA];
var element_el;
for (var el in element) {
element_el = element[el];
if (element_el === null) {
console.error('element[%s] is null', el);
delete element[el];
continue;
}
if (exp.isFolderData(element_el)) {
if (nbMetadataFolders !== 0) {
debug("Multiple metadata files in folder");
delete element[el];
}
nbMetadataFolders++;
continue;
}
if (!exp.isFile(element_el, true) && !exp.isFolder(element_el)) {
debug("An element in ROOT was not a folder nor a file. ", element_el);
delete element[el];
continue;
}
if (exp.isFolder(element_el)) {
fixRoot(element_el);
continue;
}
if (typeof element_el === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger();
var key = Hash.createChannelId();
files_data[id] = {
href: exp.cryptor.encrypt(element_el),
filename: el
};
element[key] = id;
delete element[el];
}
if (typeof element_el === "number") {
var data = files_data[element_el] || static_data[element_el];
if (!data) {
debug("An element in ROOT doesn't have associated data", element_el, el);
delete element[el];
}
}
}
};
var fixTrashRoot = function () {
if (sharedFolder) { return; }
if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; }
var tr = files[TRASH];
var toClean;
var addToClean = function (obj, idx, el) {
if (typeof(obj) !== "object") { toClean.push(idx); return; }
// shared folders have their own userObject
if (exp.isSharedFolder(obj.element)) { return; }
if (!exp.isFile(obj.element, true) &&
!exp.isFolder(obj.element)) { toClean.push(idx); return; }
if (!Array.isArray(obj.path)) { toClean.push(idx); return; }
if (typeof obj.element === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger();
files[FILES_DATA][id] = {
href: exp.cryptor.encrypt(obj.element),
filename: el
};
obj.element = id;
}
if (exp.isFolder(obj.element)) { fixRoot(obj.element); }
if (typeof obj.element === "number") {
var data = files[FILES_DATA][obj.element] || files[STATIC_DATA][obj.element];
if (!data) {
debug("An element in TRASH doesn't have associated data", obj.element, el);
toClean.push(idx);
}
}
};
for (var el in tr) {
if (!Array.isArray(tr[el])) {
debug("An element in TRASH root is not an array. ", tr[el]);
delete tr[el];
} else if (tr[el].length === 0) {
debug("Empty array in TRASH root. ", tr[el]);
delete tr[el];
} else {
toClean = [];
for (var j=0; j<tr[el].length; j++) {
addToClean(tr[el][j], j, el);
}
for (var i = toClean.length-1; i>=0; i--) {
tr[el].splice(toClean[i], 1);
}
}
}
};
var fixTemplate = function () {
if (sharedFolder) { return; }
if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; }
var dedup = Util.deduplicateString(files[TEMPLATE]);
if (dedup.length !== files[TEMPLATE].length) {
files[TEMPLATE] = dedup;
}
var us = files[TEMPLATE];
var rootFiles = exp.getFiles([ROOT]);
var toClean = [];
us.forEach(function (el, idx) {
if (!exp.isFile(el, true) || rootFiles.indexOf(el) !== -1) {
toClean.push(el);
return;
}
if (typeof el === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger();
files[FILES_DATA][id] = {
href: exp.cryptor.encrypt(el)
};
us[idx] = id;
return;
}
if (typeof el === "number") {
var data = files[FILES_DATA][el];
if (!data) {
debug("An element in TEMPLATE doesn't have associated data", el);
toClean.push(el);
}
}
});
toClean.forEach(function (el) {
var idx = us.indexOf(el);
if (idx !== -1) {
us.splice(idx, 1);
}
});
};
var fixFilesData = function () {
if (typeof files[FILES_DATA] !== "object") { debug("FILES_DATA was not an object"); files[FILES_DATA] = {}; }
var fd = files[FILES_DATA];
var rootFiles = exp.getFiles([ROOT, TRASH, 'hrefArray']);
var root = exp.find([ROOT]);
var toClean = [];
for (var id in fd) {
if (String(id) !== String(Number(id))) {
debug("Invalid file ID in filesData.", id);
toClean.push(id);
continue;
}
id = Number(id);
var el = fd[id];
// Clean corrupted data
if (!el || typeof(el) !== "object") {
debug("An element in filesData was not an object.", el);
toClean.push(id);
continue;
}
// Clean missing href
if (!el.href && !el.roHref) {
debug("Removing an element in filesData with a missing href.", el);
toClean.push(id);
continue;
}
var decryptedHref;
try {
decryptedHref = el.href && ((el.href.indexOf('#') !== -1) ? el.href : exp.cryptor.decrypt(el.href));
} catch (e) {}
if (decryptedHref && decryptedHref.indexOf('#') === -1) {
// If we can't decrypt the href, it means we don't have the correct secondaryKey and we're in readOnly mode:
// abort now, we won't be able to fix anything anyway
continue;
}
var parsed = Hash.parsePadUrl(decryptedHref || el.roHref);
var secret;
// Clean invalid hash
if (!parsed.hash) {
debug("Removing an element in filesData with a invalid href.", el);
toClean.push(id);
continue;
}
// Clean invalid type
if (!parsed.type) {
debug("Removing an element in filesData with a invalid type.", el);
toClean.push(id);
continue;
}
// If we have an edit link, check the view link
if (decryptedHref && parsed.hashData.type === "pad" && parsed.hashData.version) {
if (parsed.hashData.mode === "view") {
el.roHref = decryptedHref;
delete el.href;
} else if (!el.roHref) {
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret);
} else {
var parsed2 = Hash.parsePadUrl(el.roHref);
if (!parsed2.hash || !parsed2.type) {
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret);
}
}
}
// v0 hashes don't support read-only
if (parsed.hashData.version === 0) {
delete el.roHref;
}
// Fix href
if (decryptedHref && decryptedHref.slice(0,1) !== '/') {
el.href = exp.cryptor.encrypt(Hash.getRelativeHref(decryptedHref));
}
// Fix creation time
if (!el.ctime) { el.ctime = el.atime; }
// Fix title
if (!el.title) { el.title = exp.getDefaultName(parsed); }
// Fix channel
if (!el.channel) {
try {
if (!secret) {
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
}
el.channel = secret.channel;
console.log(el);
debug('Adding missing channel in filesData ', el.channel);
} catch (e) {
console.error(e);
}
}
if (!Hash.isValidChannel(el.channel)) {
// FIXME delete channel? replace with parsed.channel?
console.error('Remove invalid channel', el.channel, el);
// toClean.push(id);
}
if ((loggedIn || config.testMode) && rootFiles.indexOf(id) === -1) {
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el);
var newName = Hash.createChannelId();
root[newName] = id;
continue;
}
}
toClean.forEach(function (id) {
spliceFileData(id);
});
// make sure that links are displayed at least once in your drive if you are going to keep them
var sd = files[STATIC_DATA];
var toCleanSD = [];
for (var id2 in sd) {
id2 = Number(id2);
var el2 = sd[id2];
if (!el2 || typeof(el2) !== "object" || !el2.href) {
toCleanSD.push(id2);
continue;
}
if ((loggedIn || config.testMode) && rootFiles.indexOf(id2) === -1) {
toCleanSD.push(id2);
continue;
}
}
var spliceSD = function (id) {
if (readOnly) { return; }
delete files[STATIC_DATA][id];
};
toCleanSD.forEach(spliceSD);
};
var fixSharedFolders = function () {
if (sharedFolder) { return; }
if (typeof(files[SHARED_FOLDERS]) !== "object") { debug("SHARED_FOLDER was not an object"); files[SHARED_FOLDERS] = {}; }
var sf = files[SHARED_FOLDERS];
var rootFiles = exp.getFiles([ROOT, TRASH]);
var root = exp.find([ROOT]);
var parsed /*, secret */, el;
for (var id in sf) {
el = sf[id];
id = Number(id);
var href;
try {
href = el.href && ((el.href.indexOf('#') !== -1) ? el.href : exp.cryptor.decrypt(el.href));
} catch (e) {}
// Fix undefined hash
parsed = Hash.parsePadUrl(href || el.roHref);
if (!parsed || !parsed.hash || parsed.hash === "undefined") {
delete sf[id];
continue;
}
// Fix shared folder not displayed in root
if (rootFiles.indexOf(id) === -1) {
console.log('missing' + id);
var newName = Hash.createChannelId();
root[newName] = id;
}
}
};
var fixSharedFoldersTemp = function () {
if (sharedFolder) { return; }
if (typeof(files[SHARED_FOLDERS_TEMP]) !== "object") {
debug("SHARED_FOLDER_TEMP was not an object");
files[SHARED_FOLDERS_TEMP] = {};
}
// Remove deprecated shared folder if they were already added back
var sft = files[SHARED_FOLDERS_TEMP];
var sf = files[SHARED_FOLDERS];
for (var id in sft) {
if (sf[id]) {
delete sft[id];
}
}
};
var fixStaticData = function () {
if (!Util.isObject(files[STATIC_DATA])) {
debug("STATIC_DATA was not an object");
files[STATIC_DATA] = {};
}
};
var fixDrive = function () {
Object.keys(files).forEach(function (key) {
if (key.slice(0,1) === '/') { delete files[key]; }
});
};
fixStaticData();
fixRoot();
fixTrashRoot();
fixTemplate();
fixFilesData();
fixDrive();
fixSharedFolders();
fixSharedFoldersTemp();
var ms = (+new Date() - t0) + 'ms';
if (JSON.stringify(files) !== before) {
debug("Your file system was corrupted. It has been cleaned so that the pads you visit can be stored safely.", ms);
return;
}
debug("File system was clean.", ms);
};
return exp;
};
return module;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('./common-util'),
require('./common-hash'),
require('./common-realtime'),
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
], factory);
} else {
// unsupported initialization
}
})();

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -13,7 +13,7 @@ define([
'/customize/messages.js',
'/common/common-interface.js',
'/common/common-util.js',
'/common/outer/worker-channel.js',
'/common/events-channel.js',
'/common/outer/x2t.js',
'/components/file-saver/FileSaver.min.js',
'css!/components/bootstrap/dist/css/bootstrap.min.css',

View File

@ -5,7 +5,7 @@
define([
'/common/common-util.js',
'/file/file-crypto.js',
'/common/outer/cache-store.js',
'/common/cache-store.js',
'/components/x2js/x2js.js',
'/components/pako/dist/pako.min.js',
'/common/common-hash.js',

View File

@ -4,7 +4,7 @@
define([
'/components/localforage/dist/localforage.min.js',
'/common/outer/cache-store.js',
'/common/cache-store.js',
'/components/nthen/index.js',
], function (localForage, Cache, nThen) {
nThen(function (w) {

View File

@ -13,14 +13,14 @@ define([
'/common/common-constants.js',
'/common/cryptget.js',
'/common/cryptpad-common.js',
'/common/outer/cache-store.js',
'/common/cache-store.js',
'/common/common-interface.js',
'chainpad-netflux',
'/components/chainpad-crypto/crypto.js',
'/common/user-object.js',
'/common/clipboard.js',
'/common/outer/login-block.js',
'/common/outer/roster.js',
//'/common/outer/roster.js',
'/common/rpc.js',
'/common/pinpad.js',
'/common/outer/local-store.js',
@ -31,7 +31,7 @@ define([
'less!/customize/src/less2/pages/page-report.less',
], function ($, ApiConfig, h, Messages,
nThen, Hash, Util, Constants, Crypt, Cryptpad, Cache, UI, CPNetflux,
Crypto, UserObject, Clipboard, Block, Roster, Rpc, Pinpad, LocalStore) {
Crypto, UserObject, Clipboard, Block, /*Roster,*/ Rpc, Pinpad, LocalStore) {
var $report = $('#cp-report');
var blockHash = localStorage.Block_hash;
if (!blockHash) {
@ -226,6 +226,7 @@ define([
// Repair roster mode
/*
if (!report) {
let roster = obj.keys.roster;
let rpc, anon_rpc;
@ -269,7 +270,7 @@ define([
}));
}).nThen(next);
return;
}
}*/
// Check team drive
nThen(function (ww) {