From a8fa0f29aea939ba4b5c78c435ed53af231b99d5 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 30 Apr 2021 09:33:59 +0530 Subject: [PATCH 1/8] add an XXX for a lint error --- www/common/common-ui-elements.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 165e95f25..573247ec6 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -798,7 +798,7 @@ define([ ])).click(common.prepareFeedback(type)).click(function () { $(button).hide(); common.getSframeChannel().query("Q_AUTOSTORE_STORE", null, function (err, obj) { - waitingForStoringCb = false; + waitingForStoringCb = false; // XXX lint error, not defined var error = err || (obj && obj.error); if (error) { $(button).show(); From 8d12086ababdd23df2710222d426d89b6e515e8e Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 30 Apr 2021 09:34:21 +0530 Subject: [PATCH 2/8] check for duplicated headers --- www/checkup/main.js | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/www/checkup/main.js b/www/checkup/main.js index 68d35a12e..9414c7127 100644 --- a/www/checkup/main.js +++ b/www/checkup/main.js @@ -357,7 +357,7 @@ define([ }); assert(function (cb, msg) { - msg = msg; // XXX + msg = msg; return void cb(true); /* msg.appendChild(h('span', [ @@ -407,6 +407,41 @@ define([ }); }); + var checkDuplicateHeaders = function (url, cb) { + $.ajax(url, { + dataType: 'text', + complete: function (xhr) { + var allHeaders = xhr.getAllResponseHeaders(); + console.error(allHeaders); + var headers = {}; + + var duplicate = allHeaders.split('\n').some(function (header) { + var duplicate; + header.replace(/([^:]+):(.*)/, function (all, type, value) { + type = type.trim(); + if (typeof(headers[type]) !== 'undefined') { + duplicate = true; + } + headers[type] = value.trim(); + }); + return duplicate; + }); + + cb(!duplicate); + }, + }); + }; + + assert(function (cb, msg) { + msg.innerText = "/api/config was served with duplicated headers."; + checkDuplicateHeaders('/api/config', cb); + }); + + assert(function (cb, msg) { + msg.innerText = "/api/config was served with duplicated headers."; + checkDuplicateHeaders('/api/broadcast', cb); + }); + var row = function (cells) { return h('tr', cells.map(function (cell) { return h('td', cell); From 0822f93fcc8015743a796d7ce1e11c4124f9d0af Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 30 Apr 2021 10:02:48 +0530 Subject: [PATCH 3/8] test api headers in checkup page --- www/checkup/main.js | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/www/checkup/main.js b/www/checkup/main.js index 9414c7127..9674dfd92 100644 --- a/www/checkup/main.js +++ b/www/checkup/main.js @@ -407,15 +407,16 @@ define([ }); }); - var checkDuplicateHeaders = function (url, cb) { + var checkAPIHeaders = function (url, cb) { $.ajax(url, { dataType: 'text', complete: function (xhr) { var allHeaders = xhr.getAllResponseHeaders(); console.error(allHeaders); + var headers = {}; - var duplicate = allHeaders.split('\n').some(function (header) { + var duplicated = allHeaders.split('\n').some(function (header) { var duplicate; header.replace(/([^:]+):(.*)/, function (all, type, value) { type = type.trim(); @@ -427,19 +428,35 @@ define([ return duplicate; }); - cb(!duplicate); + if (duplicated) { return void cb(false); } + + var expect = { + 'cross-origin-resource-policy': 'cross-origin', + }; + var incorrect = Object.keys(expect).some(function (k) { + var response = xhr.getResponseHeader(k); + if (response !== expect[k]) { + return true; + } + }); + + cb(!incorrect); }, }); }; + var INCORRECT_HEADER_TEXT = ' was served with duplicated or incorrect headers. Compare your reverse-proxy configuration against the provided example.'; + assert(function (cb, msg) { - msg.innerText = "/api/config was served with duplicated headers."; - checkDuplicateHeaders('/api/config', cb); + var url = '/api/config'; + msg.innerText = url + INCORRECT_HEADER_TEXT; + checkAPIHeaders(url, cb); }); assert(function (cb, msg) { - msg.innerText = "/api/config was served with duplicated headers."; - checkDuplicateHeaders('/api/broadcast', cb); + var url = '/api/broadcast'; + msg.innerText = url + INCORRECT_HEADER_TEXT; + checkAPIHeaders(url, cb); }); var row = function (cells) { From bd37e45eb44b72471098d31cd8278eca5a25aa9a Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 30 Apr 2021 10:38:03 +0530 Subject: [PATCH 4/8] disable some seemingly problematic server code --- server.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index 443c16a52..dab82fe77 100644 --- a/server.js +++ b/server.js @@ -105,13 +105,15 @@ var setHeaders = (function () { } if (Object.keys(headers).length) { return function (req, res) { + // 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) { + 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', }); @@ -120,11 +122,13 @@ var setHeaders = (function () { // Don't set CSP headers on /api/config 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) { + /* + if (Env.NO_SANDBOX) { applyHeaderMap(res, { "Cross-Origin-Resource-Policy": 'cross-origin', }); } + */ return; } applyHeaderMap(res, { From 8c61948d02e40fd13e2d0cf2154c34df62add47e Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 30 Apr 2021 14:48:22 +0530 Subject: [PATCH 5/8] implement SET_ADMIN_EMAIL and SET_SUPPORT_MAILBOX decrees and update changelog --- CHANGELOG.md | 9 +++++++++ lib/commands/core.js | 4 ++++ lib/decrees.js | 43 ++++++++++++++++++++++++++++++++----------- lib/metadata.js | 5 ++--- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b6a59683..ea1d3ced2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ * reminders in calendars * import/export * include LICENSE for ical.js + * translations + * out of BETA + * available from user admin menu * use a specific version of bootstrap-tokenfield in bower.json * don't create readmes * support displaying a roadmap in static pages' footer @@ -18,6 +21,12 @@ * lock sheets faster when applying checkpoints * guard against undefined checkpoints * don't spam users with prompts to checkpoints when they can't +* decrees + * SET_ADMIN_EMAIL + * SET_SUPPORT_MAILBOX +* Add DAPSI to our sponsor list +* checkup + * check for duplicate or incorrect headers # 4.4.0 diff --git a/lib/commands/core.js b/lib/commands/core.js index 42ed0455b..a0be7cd67 100644 --- a/lib/commands/core.js +++ b/lib/commands/core.js @@ -13,6 +13,10 @@ Core.isValidId = function (chan) { [32, 33, 48].indexOf(chan.length) > -1; }; +Core.isValidPublicKey = function (owner) { + return typeof(owner) === 'string' && owner.length === 44; +}; + var makeToken = Core.makeToken = function () { return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)) .toString(16); diff --git a/lib/decrees.js b/lib/decrees.js index fca8fb331..eed840057 100644 --- a/lib/decrees.js +++ b/lib/decrees.js @@ -1,4 +1,5 @@ var Decrees = module.exports; +var Core = require("./commands/core"); /* Admin decrees which modify global server state @@ -29,6 +30,10 @@ SET_LAST_BROADCAST_HASH SET_SURVEY_URL SET_MAINTENANCE +// EASIER CONFIG +SET_ADMIN_EMAIL +SET_SUPPORT_MAILBOX + NOT IMPLEMENTED: // RESTRICTED REGISTRATION @@ -37,9 +42,11 @@ REVOKE_INVITE REDEEM_INVITE // 2.0 -Env.adminEmail -Env.supportMailbox Env.DEV_MODE || Env.FRESH_MODE, + +ADD_ADMIN_KEY +RM_ADMIN_KEY + */ var commands = {}; @@ -88,6 +95,20 @@ var isNonNegativeNumber = function (n) { }; */ +var default_validator = function () { return true; }; +var makeGenericSetter = function (attr, validator) { + validator = validator || default_validator; + return function (Env, args) { + if (!validator(args)) { + throw new Error("INVALID_ARGS"); + } + var value = args[0]; + if (value === Env[attr]) { return false; } + Env[attr] = value; + return true; + }; +}; + var isInteger = function (n) { return !(typeof(n) !== 'number' || isNaN(n) || (n % 1) !== 0); }; @@ -97,15 +118,7 @@ var args_isInteger = function (args) { }; var makeIntegerSetter = function (attr) { - return function (Env, args) { - if (!args_isInteger(args)) { - throw new Error('INVALID_ARGS'); - } - var integer = args[0]; - if (integer === Env[attr]) { return false; } - Env[attr] = integer; - return true; - }; + return makeGenericSetter(attr, args_isInteger); }; // CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_MAX_UPLOAD_SIZE', [50 * 1024 * 1024]]], console.log) @@ -130,6 +143,14 @@ var args_isString = function (args) { return Array.isArray(args) && typeof(args[0]) === "string"; }; +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_ADMIN_EMAIL', ['admin@website.tld']]], console.log) +commands.SET_ADMIN_EMAIL = makeGenericSetter('adminEmail', args_isString); + +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_SUPPORT_MAILBOX', ["Tdz6+fE9N9XXBY93rW5qeNa/k27yd40c0vq7EJyt7jA="]]], console.log) +commands.SET_SUPPORT_MAILBOX = makeGenericSetter('supportMailbox', function (args) { + return args_isString(args) && Core.isValidPublicKey(args[0]); +}); + // Maintenance: Empty string or an object with a start and end time var isNumber = function (value) { return typeof(value) === "number" && !isNaN(value); diff --git a/lib/metadata.js b/lib/metadata.js index 97f2e484a..d320a5c9b 100644 --- a/lib/metadata.js +++ b/lib/metadata.js @@ -1,4 +1,5 @@ var Meta = module.exports; +var Core = require("./commands/core"); var deduplicate = require("./common-util").deduplicateString; @@ -35,9 +36,7 @@ the owners field is guaranteed to exist. var commands = {}; -var isValidPublicKey = function (owner) { - return typeof(owner) === 'string' && owner.length === 44; -}; +var isValidPublicKey = Core.isValidPublicKey; // isValidPublicKey is a better indication of what the above function does // I'm preserving this function name in case we ever want to expand its From c1c9e5ade8ed7c2ab2f7ab66a62b03ea0fd7b1f9 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 30 Apr 2021 11:48:41 +0200 Subject: [PATCH 6/8] lint compliance --- www/common/common-ui-elements.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 573247ec6..2c1b5f263 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -798,7 +798,6 @@ define([ ])).click(common.prepareFeedback(type)).click(function () { $(button).hide(); common.getSframeChannel().query("Q_AUTOSTORE_STORE", null, function (err, obj) { - waitingForStoringCb = false; // XXX lint error, not defined var error = err || (obj && obj.error); if (error) { $(button).show(); From 280e96287b60989fa2ec3ec06cc503ebbda9f4a8 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 30 Apr 2021 16:56:27 +0200 Subject: [PATCH 7/8] Fix context menu for markdown extensions and add download --- www/common/diffMarked.js | 1 + www/common/inner/common-mediatag.js | 54 +++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index f2fb97f24..cb95f37aa 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -40,6 +40,7 @@ define([ Mermaid = _Mermaid; Mermaid.initialize({ gantt: { axisFormat: '%m-%d', }, + flowchart: { htmlLabels: false, }, theme: (window.CryptPad_theme === 'dark') ? 'dark' : 'default', "themeCSS": mermaidThemeCSS, }); diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index 775f1f34a..884709071 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -386,11 +386,11 @@ define([ 'tabindex': '-1', 'data-icon': "fa-eye", }, Messages.pad_mediatagPreview)), - h('li.cp-svg', h('a.cp-app-code-context-openin.dropdown-item', { + h('li', h('a.cp-app-code-context-openin.dropdown-item', { 'tabindex': '-1', 'data-icon': "fa-external-link", }, Messages.pad_mediatagOpen)), - h('li.cp-svg', h('a.cp-app-code-context-share.dropdown-item', { + h('li', h('a.cp-app-code-context-share.dropdown-item', { 'tabindex': '-1', 'data-icon': "fa-shhare-alt", }, Messages.pad_mediatagShare)), @@ -398,7 +398,7 @@ define([ 'tabindex': '-1', 'data-icon': "fa-cloud-upload", }, Messages.pad_mediatagImport)), - h('li', h('a.cp-app-code-context-download.dropdown-item', { + h('li.cp-svg', h('a.cp-app-code-context-download.dropdown-item', { 'tabindex': '-1', 'data-icon': "fa-download", }, Messages.download_mt_button)), @@ -429,6 +429,54 @@ define([ common.importMediaTag($mt); } else if ($this.hasClass("cp-app-code-context-download")) { + if ($mt.is('pre.mermaid') || $mt.is('pre.markmap')) { + (function () { + var name = 'image.svg'; // XXX + var svg = $mt.find('svg')[0].cloneNode(true); + $(svg).attr('xmlns', 'http://www.w3.org/2000/svg').attr('width', $mt.width()).attr('height', $mt.height()); + $(svg).find('foreignObject').each(function (i, el) { + var $el = $(el); + $el.find('br').after('\n'); + $el.find('br').remove(); + var t = $el[0].innerText || $el[0].textContent; + t.split('\n').forEach(function (text, i) { + var dy = (i+1)+'em'; + $el.after(h('text', {y:0, dy:dy, style: ''}, text)); + }); + $el.remove(); + }); + var html = svg.outerHTML; + html = html.replace('
', '
'); + var b = new Blob([html], { type: 'image/svg+xml' }); + window.saveAs(b, name); + })(); + return; + } + if ($mt.is('pre.mathjax')) { + (function () { + var name = 'image.png'; // XXX + var svg = $mt.find('> span > svg')[0]; + var clone = svg.cloneNode(true); + var html = clone.outerHTML; + var b = new Blob([html], { type: 'image/svg+xml' }); + var blobURL = URL.createObjectURL(b); + var i = new Image(); + i.onload = function () { + var canvas = document.createElement('canvas'); + canvas.width = i.width; + canvas.height = i.height; + var context = canvas.getContext('2d'); + context.drawImage(i, 0, 0, i.width, i.height); + var png = canvas.toDataURL(); + var link = document.createElement('a'); + link.download = name; + link.href = png; + link.click(); + }; + i.src = blobURL; + })(); + return; + } var media = Util.find($mt, [0, '_mediaObject']); if (!media) { return void console.error('no media'); } if (!media.complete) { return void UI.warn(Messages.mediatag_notReady); } From 3fbfea516f335eb7d935e6f0dc5ab438e8c71125 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 30 Apr 2021 17:07:21 +0200 Subject: [PATCH 8/8] Fix mathjax export --- www/common/inner/common-mediatag.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index 884709071..9f7506d61 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -467,11 +467,9 @@ define([ canvas.height = i.height; var context = canvas.getContext('2d'); context.drawImage(i, 0, 0, i.width, i.height); - var png = canvas.toDataURL(); - var link = document.createElement('a'); - link.download = name; - link.href = png; - link.click(); + canvas.toBlob(function (blob) { + window.saveAs(blob, name); + }); }; i.src = blobURL; })();