diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 70da31523..abe9c7938 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -162,7 +162,7 @@ define([ } var parsed = Hash.parsePadUrl(data.href || data.roHref); - if (!data.noEditPassword && owned && parsed.hashData.type === 'pad') { + if (!data.noEditPassword && owned && parsed.hashData.type === 'pad' && parsed.type !== "sheet") { // FIXME SHEET fix password change for sheets var sframeChan = common.getSframeChannel(); var changePwTitle = Messages.properties_changePassword; var changePwConfirm = Messages.properties_confirmChange; @@ -412,6 +412,7 @@ define([ if (!friend.notifications || !friend.curvePublic) { return; } common.mailbox.sendTo("SHARE_PAD", { href: href, + password: config.password, name: myName, title: title }, { diff --git a/www/common/notifications.js b/www/common/notifications.js index 136117c6f..1d7f2921c 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -57,7 +57,12 @@ define([ .html(Messages._getKey(key, [msg.content.name || Messages.anonymous, msg.content.title])); $(el).find('.cp-notification-content').addClass("cp-clickable") .click(function () { - common.openURL(msg.content.href); + var todo = function () { common.openURL(msg.content.href); }; + if (!msg.content.password) { return void todo(); } + common.getSframeChannel().query('Q_SESSIONSTORAGE_PUT', { + key: 'newPadPassword', + value: msg.content.password + }, todo); }); $(el).find('.cp-notification-dismiss').css('display', 'flex'); }; diff --git a/www/common/onlyoffice/main.js b/www/common/onlyoffice/main.js index d056cad42..6bd6c7df5 100644 --- a/www/common/onlyoffice/main.js +++ b/www/common/onlyoffice/main.js @@ -102,9 +102,12 @@ define([ Cryptpad.onlyoffice.onEvent.reg(function (obj) { if (obj.ev === 'MESSAGE' && !/^cp\|/.test(obj.data)) { try { + var validateKey = obj.data.validateKey || true; + var skipCheck = validateKey === true; + var msg = obj.data.msg; obj.data = { - msg: JSON.parse(Utils.crypto.decrypt(obj.data, Utils.secret.keys.validateKey)), - hash: obj.data.slice(0,64) + msg: JSON.parse(Utils.crypto.decrypt(msg, validateKey, skipCheck)), + hash: msg.slice(0,64) }; } catch (e) { console.error(e); diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index 0ab804dcf..10e61fb60 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -1,6 +1,7 @@ define([ '/common/common-messaging.js', -], function (Messaging) { + '/common/common-hash.js', +], function (Messaging, Hash) { var getRandomTimeout = function (ctx) { var lag = ctx.store.realtime.getLag().lag || 0; @@ -156,6 +157,50 @@ define([ cb(true); }; + // Hide duplicates when receiving a SHARE_PAD notification: + // Keep only one notification per channel: the stronger and more recent one + var channels = {}; + handlers['SHARE_PAD'] = function (ctx, box, data, cb) { + var msg = data.msg; + var hash = data.hash; + var content = msg.content; + // content.name, content.title, content.href, content.password + + var channel = Hash.hrefToHexChannelId(content.href, content.password); + var parsed = Hash.parsePadUrl(content.href); + var mode = parsed.hashData && parsed.hashData.mode || 'n/a'; + + var old = channels[channel]; + var toRemove; + if (old) { + // New hash is weaker, ignore + if (old.mode === 'edit' && mode === 'view') { + return void cb(true); + } + // New hash is not weaker, clear the old one + toRemove = old.data; + } + + // Update the data + channels[channel] = { + mode: mode, + data: { + type: box.type, + hash: hash + } + }; + + cb(false, toRemove); + }; + removeHandlers['SHARE_PAD'] = function (ctx, box, data, hash) { + var content = data.content; + var channel = Hash.hrefToHexChannelId(content.href, content.password); + var old = channels[channel]; + if (old && old.data && old.data.hash === hash) { + delete channels[channel]; + } + }; + return { add: function (ctx, box, data, cb) { /** diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index 550573411..90d536bc3 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -157,6 +157,7 @@ proxy.mailboxes = { var openChannel = function (ctx, type, m, onReady) { var box = ctx.boxes[type] = { + type: type, queue: [], // Store the messages to send when the channel is ready history: [], // All the hashes loaded from the server in corretc order content: {}, // Content of the messages that should be displayed @@ -228,8 +229,8 @@ proxy.mailboxes = { msg: msg, hash: hash }; - Handlers.add(ctx, box, message, function (toDismiss) { - if (toDismiss) { + Handlers.add(ctx, box, message, function (dismissed, toDismiss) { + if (dismissed) { // This message should be removed dismiss(ctx, { type: type, hash: hash @@ -238,6 +239,11 @@ proxy.mailboxes = { }); return; } + if (toDismiss) { // List of other messages to remove + dismiss(ctx, toDismiss, '', function () { + console.log('Notification handled automatically'); + }); + } box.content[hash] = msg; showMessage(ctx, type, message); }); diff --git a/www/common/outer/onlyoffice.js b/www/common/outer/onlyoffice.js index 3b57b4123..70bc413ec 100644 --- a/www/common/outer/onlyoffice.js +++ b/www/common/outer/onlyoffice.js @@ -26,7 +26,10 @@ define([ if (!c.id) { c.id = chan.wc.myID + '-' + client; } chan.history.forEach(function (msg) { - ctx.emit('MESSAGE', msg, [client]); + ctx.emit('MESSAGE', { + msg: msg, + validateKey: chan.validateKey + }, [client]); }); // ==> And push the new tab to the list @@ -37,7 +40,8 @@ define([ var onOpen = function (wc) { ctx.channels[channel] = ctx.channels[channel] || { - history: [] + history: [], + validateKey: obj.validateKey }; chan = ctx.channels[channel]; @@ -61,7 +65,10 @@ define([ }); wc.on('message', function (msg) { chan.history.push(msg); - ctx.emit('MESSAGE', msg, chan.clients); + ctx.emit('MESSAGE', { + msg: msg, + validateKey: chan.validateKey + }, chan.clients); }); chan.wc = wc; @@ -101,6 +108,7 @@ define([ }; network.on('message', function (msg, sender) { + if (!ctx.channels[channel]) { return; } var hk = network.historyKeeper; if (sender !== hk) { return; } @@ -115,7 +123,12 @@ define([ // Keep only metadata messages for the current channel if (parsed.channel && parsed.channel !== channel) { return; } // Ignore the metadata message - if (parsed.validateKey && parsed.channel) { return; } + if (parsed.validateKey && parsed.channel) { + if (!chan.validateKey) { + chan.validateKey = parsed.validateKey; + } + return; + } // End of history: emit READY if (parsed.state && parsed.state === 1 && parsed.channel) { ctx.emit('READY', '', chan.clients); @@ -132,7 +145,9 @@ define([ if (hash === chan.lastKnownHash || hash === chan.lastCpHash) { return; } chan.lastKnownHash = hash; - ctx.emit('MESSAGE', msg, chan.clients); + ctx.emit('MESSAGE', { + msg: msg, + }, chan.clients); chan.history.push(msg); }); @@ -176,7 +191,9 @@ define([ return void chan.sendMsg(data.isCp, cb); } chan.sendMsg(data.msg, cb); - ctx.emit('MESSAGE', data.msg, chan.clients.filter(function (cl) { + ctx.emit('MESSAGE', { + msg: data.msg + }, chan.clients.filter(function (cl) { return cl !== clientId; })); }; diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index e67dd5f01..e12883eb5 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -625,6 +625,11 @@ define([ 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]; diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index 017a403bb..9d8bbe6ba 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -342,7 +342,7 @@ define([ }); // Remove the elements from the old location (without unpinning) - Env.user.userObject.delete(resolved.main, waitFor()); + Env.user.userObject.delete(resolved.main, waitFor()); // FIXME waitFor() is called synchronously } } } @@ -369,7 +369,7 @@ define([ if (copy) { return; } // Remove the elements from the old location (without unpinning) - uoFrom.delete(paths, waitFor()); + uoFrom.delete(paths, waitFor()); // FIXME waitFor() is called synchronously } }); } @@ -707,6 +707,7 @@ define([ if (type === 'expirable') { return function (fileId) { var data = userObject.getFileData(fileId); + if (!data) { return; } // Don't push duplicates if (result.indexOf(data.channel) !== -1) { return; } // Return pads owned by someone else or expired by time @@ -718,6 +719,7 @@ define([ if (type === 'owned') { return function (fileId) { var data = userObject.getFileData(fileId); + if (!data) { return; } // Don't push duplicates if (result.indexOf(data.channel) !== -1) { return; } // Return owned pads @@ -729,6 +731,7 @@ define([ if (type === "pin") { return function (fileId) { var data = userObject.getFileData(fileId); + if (!data) { return; } // Don't pin pads owned by someone else if (_ownedByOther(Env, data.owners)) { return; } // Don't push duplicates diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 838f415e4..fba08279a 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -223,6 +223,11 @@ define([ sframeChan.event("EV_PAD_PASSWORD"); }; + if (!val && sessionStorage.newPadPassword) { + val = sessionStorage.newPadPassword; + delete sessionStorage.newPadPassword; + } + if (val) { password = val; Cryptpad.getFileSize(window.location.href, password, waitFor(function (e, size) { diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 303a983e0..2da2fc7d8 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -832,11 +832,17 @@ MessengerUI, Messages) { return $spin; }; - var createLimit = function (toolbar) { + var createLimit = function (toolbar, config) { var $limitIcon = $('', {'class': 'fa fa-exclamation-triangle'}); var $limit = toolbar.$userAdmin.find('.'+LIMIT_CLS).attr({ 'title': Messages.pinLimitReached }).append($limitIcon).hide(); + + var priv = config.metadataMgr.getPrivateData(); + var origin = priv.origin; + var l = document.createElement("a"); + l.href = origin; + var todo = function (e, overLimit) { if (e) { return void console.error("Unable to get the pinned usage", e); } if (overLimit) { @@ -845,7 +851,7 @@ MessengerUI, Messages) { key = 'pinLimitReachedAlertNoAccounts'; } $limit.show().click(function () { - UI.alert(Messages._getKey(key, [encodeURIComponent(window.location.hostname)]), null, true); + UI.alert(Messages._getKey(key, [encodeURIComponent(l.hostname)]), null, true); }); } }; diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json index f8618fdb9..6125abf5c 100644 --- a/www/common/translations/messages.de.json +++ b/www/common/translations/messages.de.json @@ -565,7 +565,7 @@ "download_step1": "Laden...", "download_step2": "Entschlüsselung...", "todo_title": "CryptTodo", - "todo_newTodoNamePlaceholder": "Beschreibe deine Aufgabe...", + "todo_newTodoNamePlaceholder": "Beschreibe deine Aufgabe…", "todo_newTodoNameTitle": "Diese Aufgabe zu deiner ToDo-Liste hinzufügen", "todo_markAsCompleteTitle": "Diese Aufgabe als erledigt markieren", "todo_markAsIncompleteTitle": "Diese Aufgabe als nicht erledigt markieren", @@ -830,7 +830,7 @@ "generic": { "more": "Erfahre mehr über die Nutzung von CryptPad, indem du unsere FAQ liest", "share": "Benutze das Teilen-Menü (), um Links zu generieren, die Mitarbeiter zum Lesen oder Bearbeiten einladen", - "save": "Alle Änderungen werden automatisch synchronisiert. Du misst sie also nicht speichern" + "save": "Alle Änderungen werden automatisch synchronisiert. Du musst sie also nicht selbst speichern" }, "text": { "formatting": "Du kannst die Werkzeugleiste anzeigen oder verbergen, indem du auf oder klickst", diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json index 027e75743..e0885452f 100644 --- a/www/common/translations/messages.json +++ b/www/common/translations/messages.json @@ -1075,5 +1075,33 @@ "share_withFriends": "Share", "notifications_dismiss": "Dismiss", "fm_info_sharedFolderHistory": "This is only the history of your shared folder: {0}
Your CryptDrive will stay in read-only mode while you navigate.", - "share_description": "Choose what you'd like to share and either get the link or send it directly to your CryptPad friends." + "share_description": "Choose what you'd like to share and either get the link or send it directly to your CryptPad friends.", + "supportPage": "Support", + "admin_cat_support": "Support", + "admin_supportInitHelp": "Your server is not yet configured to have a support mailbox. If you want a support mailbox to receive messages from your users, you should ask your server administrator to run the script located in \"./scripts/generate-admin-keys.js\", then store the public key in the \"config.js\" file and send you the private key.", + "admin_supportInitPrivate": "Your CryptPad instance is configured to use a support mailbox but your account doesn't have the correct private key to access it. Please use the following form to add or update the private key to your account.", + "admin_supportAddKey": "Add private key", + "admin_supportAddError": "Invalid private key", + "admin_supportInitTitle": "Support mailbox initialization", + "admin_supportInitHint": "You can configure a support mailbox in order to give users of your CryptPad instance a way to contact you securely if they have an issue with their account.", + "admin_supportListTitle": "Support mailbox", + "admin_supportListHint": "Here is the list of tickets sent by the users to the support mailbox. All the administrators can see the messages and the answers. A closed ticket cannot be reopened. You can only remove (hide) closed tickets, and the removed tickets are still visible by the other administrators.", + "support_disabledTitle": "Support is not enabled", + "support_disabledHint": "This CryptPad instance is not yet configured to use a support form.", + "support_cat_new": "New ticket", + "support_formTitle": "Ticket title", + "support_formHint": "This form can be used to create a new support ticket. Using this form, you can contact the administrators to solve issues or ask any question in a secure way. Please don't create a new ticket if you already have an open ticket about the same issue, but use reply button to provide more information.", + "support_formButton": "Send", + "support_formTitleError": "Error: title is empty", + "support_formContentError": "Error: content is empty", + "support_formMessage": "Type your message...", + "support_cat_tickets": "Existing tickets", + "support_listTitle": "Support tickets", + "support_listHint": "Here is the list of tickets sent to the administrators and their answers. A closed ticket cannot be re-opened, you have to make a new one. You can hide tickets that have been closed but they will still be visible by the administrators.", + "support_answer": "Reply", + "support_close": "Close the ticket", + "support_remove": "Remove the ticket", + "support_showData": "Show/hide user data", + "support_from": "From: {0}", + "support_closed": "This ticket has been closed" } diff --git a/www/common/userObject.js b/www/common/userObject.js index a9068afe6..f39e80aee 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -311,12 +311,12 @@ define([ _getFiles[FILES_DATA] = function () { var ret = []; if (!files[FILES_DATA]) { return ret; } - return Object.keys(files[FILES_DATA]).map(Number); + return Object.keys(files[FILES_DATA]).map(Number).filter(Boolean); }; _getFiles[SHARED_FOLDERS] = function () { var ret = []; if (!files[SHARED_FOLDERS]) { return ret; } - return Object.keys(files[SHARED_FOLDERS]).map(Number); + return Object.keys(files[SHARED_FOLDERS]).map(Number).filter(Boolean); }; var getFiles = exp.getFiles = function (categories) { var ret = []; diff --git a/www/drive/inner.js b/www/drive/inner.js index 596465396..c2c0332a4 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -2173,6 +2173,7 @@ define([ pathname: "/drive/", friends: friends, title: data.title, + password: data.password, common: common, hashes: { editHash: parsed.hash @@ -3510,6 +3511,7 @@ define([ friends: friends, title: data.title, common: common, + password: data.password, hashes: { editHash: parsed.hash } @@ -3523,6 +3525,7 @@ define([ origin: APP.origin, pathname: "/" + padType + "/", friends: friends, + password: data.password, hashes: { editHash: parsed.hash, viewHash: roParsed.hash, diff --git a/www/share/inner.js b/www/share/inner.js index fbc1f48cc..d50bb9a9c 100644 --- a/www/share/inner.js +++ b/www/share/inner.js @@ -42,6 +42,7 @@ define([ var modal = f({ origin: origin, pathname: pathname, + password: priv.password, hashes: hashes, common: common, title: data.title,