Merge branch 'staging' into soon

pull/1/head
ansuz 4 years ago
commit ef604422ab

@ -1,3 +1,38 @@
# 4.6.0
## Goals
* work on polls/surveys
* stabilize and implement tests
## Update notes
* checkup/server/config
* test for anti-FLoC header
* add anti-FloC header to server so the default dev server passes all tests
* update NGINX example to avoid duplicated headers
* simplify dev server headers
* adjust table borders for dark/light mode
* say what headers are wrong
* rename exported object in `application_config_internal.js` to avoid copypasta errors
* `AppConfig.disableAnonymousPadCreation = false;`
* note that it's only enforced client-side
* update lodash devDependency
## Features
* generate `supportMailbox` keys via the admin panel
* document this
* markdown preview
* code blocks are full width
## Bug fixes
* fix opening links from temporary shared folders on iphone or other contexts that do not support shared workers
* show "features" instead of "pricing" in static pages' footer when premium subscriptions are not available
* use preferred 12/24h time format in date picker
* add error handling to admin panel decree RPCs
# 4.5.0
## Goals

@ -122,19 +122,6 @@ module.exports = {
],
*/
/* CryptPad's administration panel includes a "support" tab
* wherein administrators with a secret key can view messages
* sent from users via the encrypted forms on the /support/ page
*
* To enable this functionality:
* run `node ./scripts/generate-admin-keys.js`
* save the public key in your config in the value below
* add the private key via the admin panel
* and back it up in a secure manner
*
*/
// supportMailboxPublicKey: "",
/* We're very proud that CryptPad is available to the public as free software!
* We do, however, still need to pay our bills as we develop the platform.
*
@ -147,11 +134,6 @@ module.exports = {
*/
//removeDonateButton: false,
/* CryptPad will display a point of contact for your instance on its contact page
* (/contact.html) if you provide it below.
*/
adminEmail: 'i.did.not.read.my.config@cryptpad.fr',
/*
* By default, CryptPad contacts one of our servers once a day.
* This check-in will also send some very basic information about your instance including its

@ -43,6 +43,17 @@ define([
return Pages.externalLink(el, Pages.localizeDocsLink(href));
};
var accounts = Pages.accounts = {
donateURL: AppConfig.donateURL || "https://opencollective.com/cryptpad/",
upgradeURL: AppConfig.upgradeURL
};
Pages.areSubscriptionsAllowed = function () {
try {
return ApiConfig.allowSubscriptions && accounts.upgradeURL && !ApiConfig.restrictRegistration;
} catch (err) { return void console.error(err); }
};
var languageSelector = function () {
var options = [];
var languages = Msg._languages;
@ -94,7 +105,7 @@ define([
var imprintUrl = AppConfig.imprint && (typeof(AppConfig.imprint) === "boolean" ?
'/imprint.html' : AppConfig.imprint);
Pages.versionString = "v4.5.0";
Pages.versionString = "v4.6.0";
// used for the about menu
@ -133,7 +144,7 @@ define([
footerCol('footer_product', [
footLink('/what-is-cryptpad.html', 'topbar_whatIsCryptpad'),
Pages.docsLink,
footLink('/features.html', 'pricing'),
footLink('/features.html', Pages.areSubscriptionsAllowed()? 'pricing': 'features'), // Messages.pricing, Messages.features
Pages.githubLink,
footLink('https://opencollective.com/cryptpad/contribute/', 'footer_donate'),
]),

@ -8,10 +8,7 @@ define([
'/api/config',
'/common/common-ui-elements.js',
], function ($, h, Msg, AppConfig, LocalStore, Pages, Config, UIElements) {
var accounts = {
donateURL: AppConfig.donateURL || "https://opencollective.com/cryptpad/",
upgradeURL: AppConfig.upgradeURL
};
var accounts = Pages.accounts;
return function () {
Msg.features_f_apps_note = AppConfig.availablePadTypes.map(function (app) {
@ -145,10 +142,11 @@ define([
]),
]),
]);
var availableFeatures =
(Config.allowSubscriptions && accounts.upgradeURL && !Config.restrictRegistration) ?
[anonymousFeatures, registeredFeatures, premiumFeatures] :
[anonymousFeatures, registeredFeatures];
var availableFeatures = [
anonymousFeatures,
registeredFeatures,
Pages.areSubscriptionsAllowed() ? premiumFeatures: undefined,
];
return h('div#cp-main', [
Pages.infopageTopbar(),

@ -194,7 +194,7 @@
pre > code {
display: block;
position: relative;
width: 90%;
width: 100%;
margin: auto;
padding: 5px;
overflow-x: auto;

@ -167,6 +167,13 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# These settings prevent both NGINX and the API server
# from setting the same headers and creating duplicates
proxy_hide_header Cross-Origin-Resource-Policy;
add_header Cross-Origin-Resource-Policy cross-origin;
proxy_hide_header Cross-Origin-Embedder-Policy;
add_header Cross-Origin-Embedder-Policy require-corp;
}
# encrypted blobs are immutable and are thus cached for a year

@ -348,6 +348,9 @@ var commands = {
CLEAR_CACHED_CHANNEL_INDEX: clearChannelIndex,
GET_CACHED_CHANNEL_INDEX: getChannelIndex,
// TODO implement admin historyTrim
// TODO implement kick from channel
// TODO implement force-disconnect user(s)?
CLEAR_CACHED_CHANNEL_METADATA: clearChannelMetadata,
GET_CACHED_CHANNEL_METADATA: getChannelMetadata,

@ -48,6 +48,7 @@ Default.httpHeaders = function () {
"X-XSS-Protection": "1; mode=block",
"X-Content-Type-Options": "nosniff",
"Access-Control-Allow-Origin": "*",
"Permissions-policy":"interest-cohort=()"
};
};
Default.mainPages = function () {

@ -701,6 +701,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
}
if (msgCount === 0 && !metadata_cache[channelName] && Server.channelContainsUser(channelName, userId)) {
// TODO this might be a good place to reject channel creation by anonymous users
handleFirstMessage(Env, channelName, metadata);
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(metadata)]);
}

2
package-lock.json generated

@ -1,6 +1,6 @@
{
"name": "cryptpad",
"version": "4.5.0",
"version": "4.6.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server",
"version": "4.5.0",
"version": "4.6.0",
"license": "AGPL-3.0+",
"repository": {
"type": "git",

@ -108,28 +108,21 @@ var setHeaders = (function () {
// apply a bunch of cross-origin headers for XLSX export in FF and printing elsewhere
applyHeaderMap(res, {
"Cross-Origin-Opener-Policy": /^\/sheet\//.test(req.url)? 'same-origin': '',
"Cross-Origin-Embedder-Policy": 'require-corp',
});
if (Env.NO_SANDBOX) { // handles correct configuration for local development
// https://stackoverflow.com/questions/11531121/add-duplicate-http-response-headers-in-nodejs
applyHeaderMap(res, {
"Cross-Origin-Resource-Policy": 'cross-origin',
"Cross-Origin-Embedder-Policy": 'require-corp',
});
}
// Don't set CSP headers on /api/config because they aren't necessary and they cause problems
// Don't set CSP headers on /api/ endpoints
// because they aren't necessary and they cause problems
// when duplicated by NGINX in production environments
if (/^\/api\/(broadcast|config)/.test(req.url)) {
/*
if (Env.NO_SANDBOX) {
applyHeaderMap(res, {
"Cross-Origin-Resource-Policy": 'cross-origin',
});
}
*/
return;
}
if (/^\/api\/(broadcast|config)/.test(req.url)) { return; }
applyHeaderMap(res, {
"Cross-Origin-Resource-Policy": 'cross-origin',
});

@ -16,6 +16,7 @@ define([
'/support/ui.js',
'/lib/datepicker/flatpickr.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'css!/lib/datepicker/flatpickr.min.css',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
@ -44,6 +45,7 @@ define([
'instanceStatus': {}
};
var Nacl = window.nacl;
var common;
var sFrameChan;
@ -54,6 +56,7 @@ define([
'cp-admin-archive',
'cp-admin-unarchive',
'cp-admin-registration',
'cp-admin-email'
],
'quota': [ // Msg.admin_cat_quota
'cp-admin-defaultlimit',
@ -71,7 +74,8 @@ define([
],
'support': [ // Msg.admin_cat_support
'cp-admin-support-list',
'cp-admin-support-init'
'cp-admin-support-init',
'cp-admin-support-priv',
],
'broadcast': [ // Msg.admin_cat_broadcast
'cp-admin-maintenance',
@ -267,8 +271,11 @@ define([
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['RESTRICT_REGISTRATION', [val]]
}, function (e) {
if (e) { UI.warn(Messages.error); console.error(e); }
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
spinner.done();
state = APP.instanceStatus.restrictRegistration;
@ -282,6 +289,45 @@ define([
return $div;
};
create['email'] = function () {
var key = 'email';
var $div = makeBlock(key, true); // Msg.admin_emailHint, Msg.admin_emailTitle, Msg.admin_emailButton
var $button = $div.find('button');
var input = h('input', {
type: 'email',
value: ApiConfig.adminEmail || ''
});
var $input = $(input);
var innerDiv = h('div.cp-admin-setlimit-form', input);
var spinner = UI.makeSpinner($(innerDiv));
$button.click(function () {
if (!$input.val()) { return; }
spinner.spin();
$button.attr('disabled', 'disabled');
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_ADMIN_EMAIL', [$input.val()]]
}, function (e, response) {
$button.removeAttr('disabled');
if (e || response.error) {
UI.warn(Messages.error);
$input.val('');
console.error(e, response);
spinner.hide();
return;
}
spinner.done();
UI.log(Messages.saved);
});
});
$button.before(innerDiv);
return $div;
};
var getPrettySize = UIElements.prettySize;
create['defaultlimit'] = function () {
@ -316,8 +362,11 @@ define([
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['UPDATE_DEFAULT_STORAGE', data]
}, function (e) {
if (e) { UI.warn(Messages.error); return void console.error(e); }
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
return void console.error(e, response);
}
var limit = getPrettySize(l);
$div.find('.cp-admin-defaultlimit-value').text(Messages._getKey('admin_limit', [limit]));
});
@ -448,8 +497,12 @@ define([
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['RM_QUOTA', data]
}, function (e) {
if (e) { UI.warn(Messages.error); console.error(e); }
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
return;
}
APP.refreshLimits();
$key.val('');
});
@ -462,8 +515,12 @@ define([
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_QUOTA', data]
}, function (e) {
if (e) { UI.warn(Messages.error); console.error(e); }
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
return;
}
APP.refreshLimits();
$key.val('');
});
@ -637,8 +694,13 @@ define([
};
var supportKey = ApiConfig.supportMailbox;
var checkAdminKey = function (priv) {
if (!supportKey) { return; }
return Hash.checkBoxKeyPair(priv, supportKey);
};
create['support-list'] = function () {
if (!supportKey || !APP.privateKey) { return; }
if (!supportKey || !APP.privateKey || !checkAdminKey(APP.privateKey)) { return; }
var $container = makeBlock('support-list'); // Msg.admin_supportListHint, .admin_supportListTitle
var $div = $(h('div.cp-support-container')).appendTo($container);
@ -898,16 +960,63 @@ define([
return $container;
};
create['support-priv'] = function () {
if (!supportKey || !APP.privateKey || !checkAdminKey(APP.privateKey)) { return; }
var checkAdminKey = function (priv) {
if (!supportKey) { return; }
return Hash.checkBoxKeyPair(priv, supportKey);
var $div = makeBlock('support-priv', true); // Msg.admin_supportPrivHint, .admin_supportPrivTitle, .admin_supportPrivButton
var $button = $div.find('button').click(function () {
$button.remove();
var $selectable = $(UI.dialog.selectable(APP.privateKey)).css({ 'max-width': '28em' });
$div.append($selectable);
});
return $div;
};
create['support-init'] = function () {
var $div = makeBlock('support-init'); // Msg.admin_supportInitHint, .admin_supportInitTitle
if (!supportKey) {
(function () {
$div.append(h('p', Messages.admin_supportInitHelp));
var button = h('button.btn.btn-primary', Messages.admin_supportInitGenerate);
var $button = $(button).appendTo($div);
$div.append($button);
var spinner = UI.makeSpinner($div);
$button.click(function () {
spinner.spin();
$button.attr('disabled', 'disabled');
var keyPair = Nacl.box.keyPair();
var pub = Nacl.util.encodeBase64(keyPair.publicKey);
var priv = Nacl.util.encodeBase64(keyPair.secretKey);
// Store the private key first. It won't be used until the decree is accepted.
sFrameChan.query("Q_ADMIN_MAILBOX", priv, function (err, obj) {
if (err || (obj && obj.error)) {
console.error(err || obj.error);
UI.warn(Messages.error);
spinner.hide();
return;
}
// Then send the decree
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_SUPPORT_MAILBOX', [pub]]
}, function (e, response) {
$button.removeAttr('disabled');
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
spinner.hide();
return;
}
spinner.done();
UI.log(Messages.saved);
supportKey = pub;
APP.privateKey = priv;
$('.cp-admin-support-init').hide();
APP.$rightside.append(create['support-list']());
APP.$rightside.append(create['support-priv']());
});
});
});
})();
return $div;
}
if (!APP.privateKey || !checkAdminKey(APP.privateKey)) {
@ -937,6 +1046,7 @@ define([
APP.privateKey = key;
$('.cp-admin-support-init').hide();
APP.$rightside.append(create['support-list']());
APP.$rightside.append(create['support-priv']());
});
});
return $div;
@ -1030,9 +1140,10 @@ define([
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_LAST_BROADCAST_HASH', [lastHash]]
}, function (e) {
if (e) {
console.error(e);
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
return;
}
console.log('lastBroadcastHash updated');
@ -1299,19 +1410,23 @@ define([
var $start = $(start);
var $end = $(end);
var is24h = false;
var dateFormat = "Y-m-d H:i";
try {
is24h = !new Intl.DateTimeFormat(navigator.language, { hour: 'numeric' }).format(0).match(/AM/);
} catch (e) {}
if (!is24h) { dateFormat = "Y-m-d h:i K"; }
var endPickr = Flatpickr(end, {
enableTime: true,
time_24hr: is24h,
dateFormat: dateFormat,
minDate: new Date()
});
Flatpickr(start, {
enableTime: true,
time_24hr: is24h,
minDate: new Date(),
dateFormat: dateFormat,
onChange: function () {
endPickr.set('minDate', new Date($start.val()));
}
@ -1336,9 +1451,10 @@ define([
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_MAINTENANCE', [data]]
}, function (e) {
if (e) {
UI.warn(Messages.error); console.error(e);
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
$button.prop('disabled', '');
return;
}
@ -1430,10 +1546,11 @@ define([
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_SURVEY_URL', [data]]
}, function (e) {
if (e) {
}, function (e, response) {
if (e || response.error) {
$button.prop('disabled', '');
UI.warn(Messages.error); console.error(e);
UI.warn(Messages.error);
console.error(e, response);
return;
}
// Maintenance applied, send notification
@ -1529,11 +1646,12 @@ define([
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'GET_WORKER_PROFILES',
}, function (e, data) {
if (e) { return void console.error(e); }
if (e || data.error) {
UI.warn(Messages.error);
return void console.error(e, data);
}
//console.info(data);
$div.find("table").remove();
process(data);
$div.append(table);
});

@ -20,7 +20,7 @@ html, body {
}
.pending {
border: 1px solid white;
border: 1px solid @cryptpad_text_col;
.fa {
margin-right: 20px;
}
@ -45,7 +45,7 @@ html, body {
table {
td {
padding: 5px;
border: 1px solid white;
border: 1px solid @cryptpad_text_col;
}
}

@ -50,6 +50,10 @@ define([
]);
};
var cacheBuster = function (url) {
return url + '?test=' + (+new Date());
};
var trimmedSafe = trimSlashes(ApiConfig.httpSafeOrigin);
var trimmedUnsafe = trimSlashes(ApiConfig.httpUnsafeOrigin);
@ -117,7 +121,7 @@ define([
var checkAvailability = function (url, cb) {
$.ajax({
url: url,
url: cacheBuster(url),
data: {},
complete: function (xhr) {
cb(xhr.status === 200);
@ -169,10 +173,13 @@ define([
}).nThen(function () {
// Iframe is loaded
clearTimeout(to);
console.log("removing sandbox iframe");
$('iframe#sbox-iframe').remove();
cb(true);
});
});
var shared_websocket;
// Test Websocket
var evWSError = Util.mkEvent(true);
assert(function (_cb, msg) {
@ -185,6 +192,7 @@ define([
}));
var ws = new WebSocket(NetConfig.getWebsocketURL());
shared_websocket = ws;
var to = setTimeout(function () {
console.error('Websocket TIMEOUT');
evWSError.fire();
@ -203,6 +211,7 @@ define([
});
// Test login block
var shared_realtime;
assert(function (_cb, msg) {
var websocketErr = "No WebSocket available";
var cb = Util.once(Util.both(_cb, function (status) {
@ -237,7 +246,7 @@ define([
var blockUrl = Login.Block.getBlockUrl(opt.blockKeys);
var blockRequest = Login.Block.serialize("{}", opt.blockKeys);
var removeRequest = Login.Block.remove(opt.blockKeys);
console.log('Test block URL:', blockUrl);
console.warn('Testing block URL (%s). One 404 is normal.', blockUrl);
var userHash = '/2/drive/edit/000000000000000000000000';
var secret = Hash.getSecrets('drive', userHash);
@ -264,7 +273,7 @@ define([
console.error("Can't create new channel. This may also be a websocket issue.");
return void cb(false);
}
RT = rt;
shared_realtime = RT = rt;
var proxy = rt.proxy;
proxy.edPublic = opt.edPublic;
proxy.edPrivate = opt.edPrivate;
@ -330,14 +339,13 @@ define([
}).nThen(function () {
cb(true);
});
});
var sheetURL = '/common/onlyoffice/v4/web-apps/apps/spreadsheeteditor/main/index.html';
assert(function (cb, msg) {
msg.innerText = "Missing HTTP headers required for .xlsx export from sheets. ";
var url = sheetURL;
var url = cacheBuster(sheetURL);
var expect = {
'cross-origin-resource-policy': 'cross-origin',
'cross-origin-embedder-policy': 'require-corp',
@ -366,37 +374,12 @@ define([
});
assert(function (cb, msg) {
msg = msg;
return void cb(true);
/*
msg.appendChild(h('span', [
"The spreadsheet editor's code was not served with the required Content-Security Policy headers. ",
"This is most often caused by incorrectly configured sandbox parameters (",
h('code', 'httpUnsafeOrigin'),
' and ',
h('code', 'httpSafeOrigin'),
' in ',
CONFIG_PATH,
"), or settings in your reverse proxy's configuration which don't match your application server's config. ",
RESTART_WARNING(),
]));
$.ajax(sheetURL, {
msg.innerText = "Missing HTTP header required to disable Google's Floc.";
$.ajax('/?'+ (+new Date()), {
complete: function (xhr) {
var csp = xhr.getResponseHeader('Content-Security-Policy');
if (!/unsafe\-eval/.test(csp)) {
// OnlyOffice requires unsafe-eval
console.error('CSP', csp);
return cb("expected 'unsafe-eval'");
}
if (!/unsafe\-inline/.test(csp)) {
// OnlyOffice also requires unsafe-inline
console.error('CSP', csp);
return cb("expected 'unsafe-inline'");
}
cb(true);
cb(xhr.getResponseHeader('permissions-policy') === 'interest-cohort=()');
},
}); */
});
});
assert(function (cb, msg) {
@ -407,17 +390,20 @@ define([
"Your browser console may provide more details as to why this resource could not be loaded. ",
]));
$.ajax('/api/broadcast', {
$.ajax(cacheBuster('/api/broadcast'), {
dataType: 'text',
complete: function (xhr) {
console.log(xhr);
cb(xhr.status === 200);
},
});
});
var checkAPIHeaders = function (url, cb) {
$.ajax(url, {
var code = function (content) {
return h('code', content);
};
var checkAPIHeaders = function (url, msg, cb) {
$.ajax(cacheBuster(url), {
dataType: 'text',
complete: function (xhr) {
var allHeaders = xhr.getAllResponseHeaders();
@ -436,15 +422,31 @@ define([
var expect = {
'cross-origin-resource-policy': 'cross-origin',
'cross-origin-embedder-policy': 'require-corp',
};
var incorrect = Object.keys(expect).some(function (k) {
var incorrect = false;
Object.keys(expect).forEach(function (k) {
var response = xhr.getResponseHeader(k);
if (response !== expect[k]) {
return true;
var expected = expect[k];
if (response !== expected) {
incorrect = true;
msg.appendChild(h('p', [
'The ',
code(k),
' header for ',
code(url),
" is '",
code(response),
"' instead of '",
code(expected),
"' as expected.",
]));
}
});
if (duplicated || incorrect) { console.error(allHeaders); }
if (duplicated || incorrect) { console.debug(allHeaders); }
cb(!duplicated && !incorrect);
},
});
@ -455,13 +457,13 @@ define([
assert(function (cb, msg) {
var url = '/api/config';
msg.innerText = url + INCORRECT_HEADER_TEXT;
checkAPIHeaders(url, cb);
checkAPIHeaders(url, msg, cb);
});
assert(function (cb, msg) {
var url = '/api/broadcast';
msg.innerText = url + INCORRECT_HEADER_TEXT;
checkAPIHeaders(url, cb);
checkAPIHeaders(url, msg, cb);
});
var setWarningClass = function (msg) {
@ -479,8 +481,9 @@ define([
'This instance does not provide a valid ',
h('code', 'adminEmail'),
' which can make it difficult to contact its adminstrator to report vulnerabilities or abusive content.',
' This can be configured in ', CONFIG_PATH(), '. ',
RESTART_WARNING(),
" This can be configured on your instance's admin panel. Use the provided ",
code("Flush cache'"),
" button for this change to take effect for all users.",
]));
cb(email);
});
@ -517,6 +520,156 @@ define([
cb(false);
});
var response = Util.response(function (err) {
console.error('SANDBOX_ERROR', err);
});
var sandboxIframe = h('iframe', {
class: 'sandbox-test',
src: cacheBuster(trimmedSafe + '/checkup/sandbox/index.html'),
});
document.body.appendChild(sandboxIframe);
var sandboxIframeReady = Util.mkEvent(true);
setTimeout(function () {
sandboxIframeReady.fire("TIMEOUT");
}, 10 * 1000);
var postMessage = function (content, cb) {
try {
var txid = Util.uid();
content.txid = txid;
response.expect(txid, cb, 15000);
sandboxIframe.contentWindow.postMessage(JSON.stringify(content), '*');
} catch (err) {
console.error(err);
}
};
var deferredPostMessage = function (content, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
nThen(function (w) {
sandboxIframeReady.reg(w(function (err) {
if (!err) { return; }
w.abort();
cb(err);
}));
}).nThen(function () {
postMessage(content, cb);
});
};
window.addEventListener('message', function (event) {
try {
var msg = JSON.parse(event.data);
if (msg.command === 'READY') { return void sandboxIframeReady.fire(); }
if (msg.q === "READY") { return; } // ignore messages from the usual sandboxed iframe
var txid = msg.txid;
if (!txid) { return console.log("no handler for ", txid); }
response.handle(txid, msg.content);
} catch (err) {
console.error(event);
console.error(err);
}
});
var parseCSP = function (CSP) {
//console.error(CSP);
var CSP_headers = {};
CSP.split(";")
.forEach(function (rule) {
rule = (rule || "").trim();
if (!rule) { return; }
var parts = rule.split(/\s/);
var first = parts[0];
var rest = rule.slice(first.length + 1);
CSP_headers[first] = rest;
//console.error(rule.trim());
//console.info("[%s] '%s'", first, rest);
});
return CSP_headers;
};
var hasUnsafeEval = function (CSP_headers) {
return /unsafe\-eval/.test(CSP_headers['script-src']);
};
var hasUnsafeInline = function (CSP_headers) {
return /unsafe\-inline/.test(CSP_headers['script-src']);
};
var hasOnlyOfficeHeaders = function (CSP_headers) {
if (!hasUnsafeEval(CSP_headers)) {
console.error("NO_UNSAFE_EVAL");
console.log(CSP_headers);
return false;
}
if (!hasUnsafeInline(CSP_headers)) {
console.error("NO_UNSAFE_INLINE");
return void false;
}
return true;
};
var CSP_WARNING = function (url) {
return h('span', [
code(url),
' does not have the required ',
code("'content-security-policy'"),
' headers set. This is most often related to incorrectly configured sandbox domains or reverse proxies.',
]);
};
assert(function (_cb, msg) {
var url = '/sheet/inner.html';
var cb = Util.once(Util.mkAsync(_cb));
msg.appendChild(CSP_WARNING(url));
deferredPostMessage({
command: 'GET_HEADER',
content: {
url: url,
header: 'content-security-policy',
},
}, function (content) {
var CSP_headers = parseCSP(content);
cb(hasOnlyOfficeHeaders(CSP_headers));
});
});
assert(function (cb, msg) {
var url = '/common/onlyoffice/v4/web-apps/apps/spreadsheeteditor/main/index.html';
msg.appendChild(CSP_WARNING(url));
deferredPostMessage({
command: 'GET_HEADER',
content: {
url: url,
header: 'content-security-policy',
},
}, function (content) {
var CSP_headers = parseCSP(content);
cb(hasOnlyOfficeHeaders(CSP_headers));
});
});
assert(function (cb, msg) {
var url = '/sheet/inner.html';
msg.appendChild(h('span', [
code(url),
' does not have the required ',
code("'cross-origin-opener-policy'"),
' headers set.',
]));
deferredPostMessage({
command: 'GET_HEADER',
content: {
url: url,
header: 'cross-origin-opener-policy',
},
}, function (content) {
cb(content === 'same-origin');
});
});
if (false) {
assert(function (cb, msg) {
msg.innerText = 'fake test to simulate failure';
@ -583,6 +736,14 @@ define([
$progress.remove();
$('body').prepend(report);
try {
console.log('closing shared websocket');
shared_websocket.close();
} catch (err) { console.error(err); }
try {
console.log('closing shared realtime');
shared_realtime.network.disconnect();
} catch (err) { console.error(err); }
}, function (i, total) {
console.log('test '+ i +' completed');
completed++;

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
</head>
<body>
<div id="cp-progress"></div>
<iframe-placeholder>

@ -0,0 +1,50 @@
define([
'jquery',
'/bower_components/tweetnacl/nacl-fast.min.js',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/checkup/app-checkup.less',
], function ($) {
var postMessage = function (content) {
window.parent.postMessage(JSON.stringify(content), '*');
};
postMessage({ command: "READY", });
var getHeaders = function (url, cb) {
$.ajax(url + "?test=" + (+new Date()), {
dataType: 'text',
complete: function (xhr) {
var allHeaders = xhr.getAllResponseHeaders();
return void cb(void 0, allHeaders, xhr);
},
});
};
var COMMANDS = {};
COMMANDS.GET_HEADER = function (content, cb) {
var url = content.url;
getHeaders(url, function (err, headers, xhr) {
cb(xhr.getResponseHeader(content.header));
});
};
window.addEventListener("message", function (event) {
if (event && event.data) {
try {
//console.log(JSON.parse(event.data));
var msg = JSON.parse(event.data);
var command = msg.command;
var txid = msg.txid;
COMMANDS[command](msg.content, function (response) {
// postMessage with same txid
postMessage({
txid: txid,
content: response,
});
});
} catch (err) {
console.error(err);
}
} else {
console.error(event);
}
});
});

@ -4,14 +4,14 @@
* file (make a copy from /customize.dist/application_config.js)
*/
define(function() {
var config = {};
var AppConfig = {};
/* Select the buttons displayed on the main page to create new collaborative sessions.
* Removing apps from the list will prevent users from accessing them. They will instead be
* redirected to the drive.
* You should never remove the drive from this list.
*/
config.availablePadTypes = ['drive', 'teams', 'pad', 'sheet', 'code', 'slide', 'poll', 'kanban', 'whiteboard',
AppConfig.availablePadTypes = ['drive', 'teams', 'pad', 'sheet', 'code', 'slide', 'poll', 'kanban', 'whiteboard',
/*'doc', 'presentation',*/ 'file', /*'todo',*/ 'contacts' /*, 'calendar' */];
/* The registered only types are apps restricted to registered users.
* You should never remove apps from this list unless you know what you're doing. The apps
@ -20,7 +20,7 @@ define(function() {
* users and these users will be redirected to the login page if they still try to access
* the app
*/
config.registeredOnlyTypes = ['file', 'contacts', 'notifications', 'support'];
AppConfig.registeredOnlyTypes = ['file', 'contacts', 'notifications', 'support'];
/* CryptPad is available is multiple languages, but only English and French are maintained
* by the developers. The other languages may be outdated, and any missing string for a langauge
@ -30,37 +30,37 @@ define(function() {
* can be found at the top of the file `/customize.dist/messages.js`. The list should only
* contain languages code ('en', 'fr', 'de', 'pt-br', etc.), not their full name.
*/
//config.availableLanguages = ['en', 'fr', 'de'];
//AppConfig.availableLanguages = ['en', 'fr', 'de'];
/* You can display a link to the imprint (legal notice) of your website in the static pages
* footer. To do so, you can either set the following value to `true` and create an imprint.html page
* in the `customize` directory. You can also set it to an absolute URL if your imprint page already exists.
*/
config.imprint = false;
// config.imprint = true;
// config.imprint = 'https://xwiki.com/en/company/legal-notice';
AppConfig.imprint = false;
// AppConfig.imprint = true;
// AppConfig.imprint = 'https://xwiki.com/en/company/legal-notice';
/* You can display a link to your own privacy policy in the static pages footer.
* To do so, set the following value to the absolute URL of your privacy policy.
*/
// config.privacy = 'https://xwiki.com/en/company/PrivacyPolicy';
// AppConfig.privacy = 'https://xwiki.com/en/company/PrivacyPolicy';
/* We (the project's developers) include the ability to display a 'Roadmap' in static pages footer.
* This is disabled by default.
* We use this to publish the project's development roadmap, but you can use it however you like.
* To do so, set the following value to an absolute URL.
*/
//config.roadmap = 'https://cryptpad.fr/kanban/#/2/kanban/view/PLM0C3tFWvYhd+EPzXrbT+NxB76Z5DtZhAA5W5hG9wo/';
//AppConfig.roadmap = 'https://cryptpad.fr/kanban/#/2/kanban/view/PLM0C3tFWvYhd+EPzXrbT+NxB76Z5DtZhAA5W5hG9wo/';
/* Cryptpad apps use a common API to display notifications to users
* by default, notifications are hidden after 5 seconds
* You can change their duration here (measured in milliseconds)
*/
config.notificationTimeout = 5000;
config.disableUserlistNotifications = false;
AppConfig.notificationTimeout = 5000;
AppConfig.disableUserlistNotifications = false;
// Update the default colors available in the whiteboard application
config.whiteboardPalette = [
AppConfig.whiteboardPalette = [
'#000000', // black
'#FFFFFF', // white
'#848484', // grey
@ -82,14 +82,14 @@ define(function() {
// Background color in the apps with centered content:
// - file app in view mode
// - rich text app when editor's width reduced in settings
config.appBackgroundColor = '#666';
AppConfig.appBackgroundColor = '#666';
// Set enableTemplates to false to remove the button allowing users to save a pad as a template
// and remove the template category in CryptDrive
config.enableTemplates = true;
AppConfig.enableTemplates = true;
// Set enableHistory to false to remove the "History" button in all the apps.
config.enableHistory = true;
AppConfig.enableHistory = true;
/* user passwords are hashed with scrypt, and salted with their username.
this value will be appended to the username, causing the resulting hash
@ -101,15 +101,15 @@ define(function() {
created. Changing it at a later time will break logins for all existing
users.
*/
config.loginSalt = '';
config.minimumPasswordLength = 8;
AppConfig.loginSalt = '';
AppConfig.minimumPasswordLength = 8;
// Amount of time (ms) before aborting the session when the algorithm cannot synchronize the pad
config.badStateTimeout = 30000;
AppConfig.badStateTimeout = 30000;
// Customize the icon used for each application.
// You can update the colors by making a copy of /customize.dist/src/less2/include/colortheme.less
config.applicationsIcon = {
AppConfig.applicationsIcon = {
file: 'cptools-file',
fileupload: 'cptools-file-upload',
folderupload: 'cptools-folder-upload',
@ -130,50 +130,49 @@ define(function() {
// Ability to create owned pads and expiring pads through a new pad creation screen.
// The new screen can be disabled by the users in their settings page
config.displayCreationScreen = true;
AppConfig.displayCreationScreen = true;
// Prevent anonymous users from storing pads in their drive
config.disableAnonymousStore = false;
// NOTE: this is only enforced client-side as the server does not distinguish between users drives and pads
AppConfig.disableAnonymousStore = false;
// Prevent anonymous users from creating new pads (they can still access and edit existing ones)
// NOTE: this is only enforced client-side and will not prevent malicious clients from storing data
AppConfig.disableAnonymousPadCreation = false;
// Hide the usage bar in settings and drive
//config.hideUsageBar = true;
//AppConfig.hideUsageBar = true;
// Disable feedback for all the users and hide the settings part about feedback
//config.disableFeedback = true;
// Add new options in the share modal (extend an existing tab or add a new tab).
// More info about how to use it on the wiki:
// https://github.com/xwiki-labs/cryptpad/wiki/Application-config#configcustomizeshareoptions
//config.customizeShareOptions = function (hashes, tabs, config) {};
//AppConfig.disableFeedback = true;
// Add code to be executed on every page before loading the user object. `isLoggedIn` (bool) is
// indicating if the user is registered or anonymous. Here you can change the way anonymous users
// work in CryptPad, use an external SSO or even force registration
// *NOTE*: You have to call the `callback` function to continue the loading process
//config.beforeLogin = function(isLoggedIn, callback) {};
//AppConfig.beforeLogin = function(isLoggedIn, callback) {};
// Add code to be executed on every page after the user object is loaded (also work for
// unregistered users). This allows you to interact with your users' drive
// *NOTE*: You have to call the `callback` function to continue the loading process
//config.afterLogin = function(api, callback) {};
//AppConfig.afterLogin = function(api, callback) {};
// Disabling the profile app allows you to import the profile informations (display name, avatar)
// from an external source and make sure the users can't change them from CryptPad.
// You can use config.afterLogin to import these values in the users' drive.
//config.disableProfile = true;
// You can use AppConfig.afterLogin to import these values in the users' drive.
//AppConfig.disableProfile = true;
// Disable the use of webworkers and sharedworkers in CryptPad.
// Workers allow us to run the websockets connection and open the user drive in a separate thread.
// SharedWorkers allow us to load only one websocket and one user drive for all the browser tabs,
// making it much faster to open new tabs.
config.disableWorkers = false;
AppConfig.disableWorkers = false;
// Teams are always loaded during the initial loading screen (for the first tab only if
// SharedWorkers are available). Allowing users to be members of multiple teams can
// make them have a very slow loading time. To avoid impacting the user experience
// significantly, we're limiting the number of teams per user to 3 by default.
// You can change this value here.
//config.maxTeamsSlots = 5;
//AppConfig.maxTeamsSlots = 5;
// Each team is considered as a registered user by the server. Users and teams are indistinguishable
// in the database so teams will offer the same storage limits as users by default.
@ -181,7 +180,7 @@ define(function() {
// We're limiting the number of teams each user is able to own to 1 in order to make sure
// users don't use "fake" teams (1 member) just to increase their storage limit.
// You can change the value here.
// config.maxOwnedTeams = 5;
// AppConfig.maxOwnedTeams = 5;
// The userlist displayed in collaborative documents is stored alongside the document data.
// Everytime someone with edit rights joins a document or modify their user data (display
@ -192,14 +191,14 @@ define(function() {
// position of other users' cursor. You can configure the number of user from which the session
// will enter into degraded mode. A big number may result in collaborative edition being broken,
// but this number depends on the network and CPU performances of each user's device.
config.degradedLimit = 8;
AppConfig.degradedLimit = 8;
// In "legacy" mode, one-time users were always creating an "anonymous" drive when visiting CryptPad
// in which they could store their pads. The new "driveless" mode allow users to open an existing
// pad without creating a drive in the background. The drive will only be created if they visit
// a different page (Drive, Settings, etc.) or try to create a new pad themselves. You can disable
// the driveless mode by changing the following value to "false"
config.allowDrivelessMode = true;
AppConfig.allowDrivelessMode = true;
return config;
return AppConfig;
});

@ -5355,10 +5355,12 @@ var PDFFunction = function PDFFunctionClosure() {
var domain = IR[1];
var range = IR[2];
var code = IR[3];
/*
var compiled = new PostScriptCompiler().compile(code, domain, range);
if (compiled) {
return new Function('src', 'srcOffset', 'dest', 'destOffset', compiled);
}
*/
(0, _util.info)('Unable to compile PS function');
var numOutputs = range.length >> 1;
var numInputs = domain.length >> 1;

@ -448,6 +448,9 @@ define([
}
};
funcs.createPad = function (cfg, cb) {
if (AppConfig.disableAnonymousPadCreation && !funcs.isLoggedIn()) {
return void UI.errorLoadingScreen(Messages.mustLogin);
}
ctx.sframeChan.query("Q_CREATE_PAD", {
owned: cfg.owned,
expire: cfg.expire,

@ -9,14 +9,17 @@ define([
var end = cfg.endpicker;
var is24h = false
var dateFormat = "Y-m-d H:i";
try {
is24h = !new Intl.DateTimeFormat(navigator.language, { hour: 'numeric' }).format(0).match(/AM/);
} catch (e) {}
if (!is24h) { dateFormat = "Y-m-d h:i K"; }
var e = $(end.input)[0];
var endPickr = Flatpickr(e, {
enableTime: true,
time_24hr: is24h,
dateFormat: dateFormat,
minDate: start.date
});
endPickr.setDate(end.date);
@ -25,6 +28,7 @@ define([
var startPickr = Flatpickr(s, {
enableTime: true,
time_24hr: is24h,
dateFormat: dateFormat,
onChange: function () {
endPickr.set('minDate', startPickr.parseDate(s.value));
}

Loading…
Cancel
Save