From e3f5c893336ffa4a2cf02f9935c802da6c084d66 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 23 Jan 2020 16:11:06 +0100 Subject: [PATCH 01/18] Remove window.location.hash and window.location.href from common-hash --- www/common/common-hash.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 85ec3b36e..90ccf805c 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -198,7 +198,13 @@ Version 1 parsed.version = 2; parsed.app = hashArr[2]; parsed.mode = hashArr[3]; - parsed.key = hashArr[4]; + + // Check if the key is a channel ID + if (/^[a-f0-9]{32,34}$/.test(hashArr[4])) { + parsed.channel = hashArr[4]; + } else { + parsed.key = hashArr[4]; + } options = hashArr.slice(5); parsed.password = options.indexOf('p') !== -1; @@ -345,7 +351,7 @@ Version 1 secret.version = 2; secret.type = type; }; - if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) { + if (!secretHash) { generate(); return secret; } else { @@ -355,12 +361,7 @@ Version 1 if (!type) { throw new Error("getSecrets with a hash requires a type parameter"); } parsed = parseTypeHash(type, secretHash); hash = secretHash; - } else { - var pHref = parsePadUrl(window.location.href); - parsed = pHref.hashData; - hash = pHref.hash; } - //var hash = secretHash || window.location.hash.slice(1); if (hash.length === 0) { generate(); return secret; From 0ad96e0966faf660cc3ba8a62736b3e370e2f44a Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Jan 2020 12:18:25 +0100 Subject: [PATCH 02/18] Hide the crypto keys from the hash --- www/common/common-hash.js | 42 ++++++++- www/common/cryptpad-common.js | 40 ++++++--- www/common/onlyoffice/main.js | 10 +++ www/common/outer/async-store.js | 39 +++++++- www/common/outer/store-rpc.js | 1 + www/common/sframe-app-outer.js | 11 +++ www/common/sframe-common-outer.js | 143 ++++++++++++++++++++++++------ www/poll/main.js | 11 +++ 8 files changed, 251 insertions(+), 46 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 90ccf805c..b92aea475 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -60,6 +60,18 @@ var factory = function (Util, Crypto, Nacl) { return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass; } }; + Hash.getHiddenHashFromKeys = function (type, secret, opts) { + var mode = (secret.keys && secret.keys.editKeyStr) ? 'edit' : 'view'; + var pass = secret.password ? 'p/' : ''; + var hash = '/2/' + secret.type + '/' + mode + '/' + secret.channel + '/' + pass; + var href = '/' + type + '/#' + hash; + var parsed = Hash.parsePadUrl(href); + if (parsed.hashData && parsed.hashData.getHash) { + return parsed.hashData.getHash(opts || {}); + } + return hash; + }; + var getFileHashFromKeys = Hash.getFileHashFromKeys = function (secret) { var version = secret.version; var data = secret.keys; @@ -192,6 +204,13 @@ Version 1 if (opts.present) { hash += 'present/'; } return hash; }; + parsed.getOptions = function () { + return { + embed: parsed.embed, + present: parsed.present, + ownerKey: parsed.ownerKey + }; + }; return parsed; } if (hashArr[1] && hashArr[1] === '2') { // Version 2 @@ -221,6 +240,13 @@ Version 1 if (opts.present) { hash += 'present/'; } return hash; }; + parsed.getOptions = function () { + return { + embed: parsed.embed, + present: parsed.present, + ownerKey: parsed.ownerKey + }; + }; return parsed; } return parsed; @@ -256,6 +282,13 @@ Version 1 if (opts.present) { hash += 'present/'; } return hash; }; + parsed.getOptions = function () { + return { + embed: parsed.embed, + present: parsed.present, + ownerKey: parsed.ownerKey + }; + }; return parsed; } return parsed; @@ -309,6 +342,10 @@ Version 1 url += '#' + hash; return url; }; + ret.getOptions = function () { + if (!ret.hashData || !ret.hashData.getOptions) { return {}; } + return ret.hashData.getOptions(); + }; if (!/^https*:\/\//.test(href)) { idx = href.indexOf('/#'); @@ -497,8 +534,9 @@ Version 1 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) { return; } - if (!/^[a-zA-Z0-9+-/=]+$/.test(parsed.hashData.key)) { return; } + if (!parsed.hashData.key && !parsed.hashData.channel) { return; } + if (parsed.hashData.key && !/^[a-zA-Z0-9+-/=]+$/.test(parsed.hashData.key)) { return; } + if (parsed.hashData.channel && !/^[a-f0-9]{32,34}$/.test(parsed.hashData.channel)) { return; } } } return true; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index a0057c59a..0be93e14c 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -49,6 +49,12 @@ define([ account: {}, }; + // Store the href in memory + // This is a placeholder value overriden in common.ready from sframe-common-outer + var currentPad = { + href: window.location.href + }; + // COMMON common.getLanguage = function () { return Messages._languageUsed; @@ -374,7 +380,7 @@ define([ common.getMetadata = function (cb) { - var parsed = Hash.parsePadUrl(window.location.href); + var parsed = Hash.parsePadUrl(currentPad.href); postMessage("GET_METADATA", parsed && parsed.type, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(null, obj); @@ -394,7 +400,7 @@ define([ common.setPadAttribute = function (attr, value, cb, href) { cb = cb || function () {}; - href = Hash.getRelativeHref(href || window.location.href); + href = Hash.getRelativeHref(href || currentPad.href); postMessage("SET_PAD_ATTRIBUTE", { href: href, attr: attr, @@ -405,7 +411,7 @@ define([ }); }; common.getPadAttribute = function (attr, cb, href) { - href = Hash.getRelativeHref(href || window.location.href); + href = Hash.getRelativeHref(href || currentPad.href); if (!href) { return void cb('E404'); } @@ -505,7 +511,7 @@ define([ }; common.saveAsTemplate = function (Cryptput, data, cb) { - var p = Hash.parsePadUrl(window.location.href); + var p = Hash.parsePadUrl(currentPad.href); if (!p.type) { return; } // PPP: password for the new template? var hash = Hash.createRandomHash(p.type); @@ -543,7 +549,7 @@ define([ var href = data.href; var parsed = Hash.parsePadUrl(href); - var parsed2 = Hash.parsePadUrl(window.location.href); + var parsed2 = Hash.parsePadUrl(currentPad.href); if(!parsed) { throw new Error("Cannot get template hash"); } postMessage("INCREMENT_TEMPLATE_USE", href); @@ -601,7 +607,7 @@ define([ var fileHost = Config.fileHost || window.location.origin; var data = common.fromFileData; var parsed = Hash.parsePadUrl(data.href); - var parsed2 = Hash.parsePadUrl(window.location.href); + var parsed2 = Hash.parsePadUrl(currentPad.href); var hash = parsed.hash; var name = data.title; var secret = Hash.getSecrets('file', hash, data.password); @@ -660,7 +666,7 @@ define([ // Forget button common.moveToTrash = function (cb, href) { - href = href || window.location.href; + href = href || currentPad.href; postMessage("MOVE_TO_TRASH", { href: href }, cb); }; @@ -668,7 +674,7 @@ define([ common.setPadTitle = function (data, cb) { if (!data || typeof (data) !== "object") { return cb ('Data is not an object'); } - var href = data.href || window.location.href; + var href = data.href || currentPad.href; var parsed = Hash.parsePadUrl(href); if (!parsed.hash) { return cb ('Invalid hash'); } data.href = parsed.getUrl({present: parsed.present}); @@ -698,7 +704,7 @@ define([ if (obj.error !== "EAUTH") { console.log("unable to set pad title"); } return void cb(obj.error); } - cb(); + cb(null, obj); }); }; @@ -755,6 +761,13 @@ define([ cb(void 0, data); }); }; + // Get data about a given channel: use with hidden hashes + common.getPadDataFromChannel = function (obj, cb) { + if (!obj || !obj.channel || !obj.edit) { return void cb('EINVAL'); } + postMessage("GET_PAD_DATA_FROM_CHANNEL", obj, function (data) { + cb(void 0, data); + }); + }; // Admin @@ -1608,7 +1621,7 @@ define([ hashes = Hash.getHashes(secret); return void cb(null, hashes); } - var parsed = Hash.parsePadUrl(window.location.href); + var parsed = Hash.parsePadUrl(currentPad.href); if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); } hashes = Hash.getHashes(secret); @@ -1679,7 +1692,7 @@ define([ LocalStore.logout(); // redirect them to log in, and come back when they're done. - sessionStorage.redirectTo = window.location.href; + sessionStorage.redirectTo = currentPad.href; window.location.href = '/login/'; }; @@ -1780,6 +1793,11 @@ define([ return function (f, rdyCfg) { rdyCfg = rdyCfg || {}; + + if (rdyCfg.currentPad) { + currentPad = rdyCfg.currentPad; + } + if (initialized) { return void setTimeout(function () { f(void 0, env); }); } diff --git a/www/common/onlyoffice/main.js b/www/common/onlyoffice/main.js index b3a896360..600901872 100644 --- a/www/common/onlyoffice/main.js +++ b/www/common/onlyoffice/main.js @@ -9,6 +9,7 @@ define([ var requireConfig = RequireConfig(); // Loaded in load #2 + var hash, href; nThen(function (waitFor) { DomReady.onReady(waitFor()); }).nThen(function (waitFor) { @@ -19,6 +20,13 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; + + // Hidden hash + hash = window.location.hash; + href = window.location.href; + if (window.history && window.history.replaceState) { + window.history.replaceState({}, window.document.title, '#'); + } document.getElementById('sbox-iframe').setAttribute('src', ApiConfig.httpSafeOrigin + window.location.pathname + 'inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); @@ -144,6 +152,8 @@ define([ }); }; SFCommonO.start({ + hash: hash, + href: href, type: 'oo', useCreationScreen: true, addData: addData, diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 681f1d575..4b2fda935 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1016,8 +1016,12 @@ define([ if (title.trim() === "") { title = UserObject.getDefaultName(p); } - if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); } - if (p.type === "debug") { return void cb(); } + if (AppConfig.disableAnonymousStore && !store.loggedIn) { + return void cb({ notStored: true }); + } + if (p.type === "debug") { + return void cb({ notStored: true }); + } var channelData = Store.channels && Store.channels[channel]; @@ -1108,7 +1112,7 @@ define([ postMessage(clientId, "AUTOSTORE_DISPLAY_POPUP", { autoStore: autoStore }); - return void cb(); + return void cb({ notStored: true }); } else { var roHref; if (h.mode === "view") { @@ -1187,7 +1191,9 @@ define([ }); cb(list); }; - // Get the first pad we can find in any of our managers and return its file data + + // Get the first pad we can find in any of our drives and return its file data + // NOTE: This is currently only used for template: this won't search inside shared folders Store.getPadData = function (clientId, id, cb) { var res = {}; getAllStores().some(function (s) { @@ -1199,6 +1205,31 @@ define([ cb(res); }; + Store.getPadDataFromChannel = function (clientId, obj, cb) { + var channel = obj.channel; + var edit = obj.edit; + var res; + var viewRes; + getAllStores().some(function (s) { + var chans = s.manager.findChannel(channel); + if (!Array.isArray(chans)) { return; } + return chans.some(function (pad) { + if (!pad || !pad.data) { return; } + var data = pad.data; + // We've found a match: return the value and stop the loops + if ((edit && data.href) || (!edit && data.roHref)) { + res = data; + return true; + } + // We've found a weaker match: store it for now + if (edit && !viewRes && data.roHref) { + viewRes = data; + } + }); + }); + // Call back with the best value we can get + cb(res || viewRes || {}); + }; // Messaging (manage friends from the userlist) Store.answerFriendRequest = function (clientId, obj, cb) { diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index 41963402b..41a3f7a0e 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -50,6 +50,7 @@ define([ GET_TEMPLATES: Store.getTemplates, GET_SECURE_FILES_LIST: Store.getSecureFilesList, GET_PAD_DATA: Store.getPadData, + GET_PAD_DATA_FROM_CHANNEL: Store.getPadDataFromChannel, GET_STRONGER_HASH: Store.getStrongerHash, INCREMENT_TEMPLATE_USE: Store.incrementTemplateUse, GET_SHARED_FOLDER: Store.getSharedFolder, diff --git a/www/common/sframe-app-outer.js b/www/common/sframe-app-outer.js index cc4d5fcb3..563430c42 100644 --- a/www/common/sframe-app-outer.js +++ b/www/common/sframe-app-outer.js @@ -8,6 +8,7 @@ define([ ], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { var requireConfig = RequireConfig(); + var hash, href; nThen(function (waitFor) { DomReady.onReady(waitFor()); }).nThen(function (waitFor) { @@ -18,6 +19,14 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; + + // Hidden hash + hash = window.location.hash; + href = window.location.href; + if (window.history && window.history.replaceState) { + window.history.replaceState({}, window.document.title, '#'); + } + document.getElementById('sbox-iframe').setAttribute('src', ApiConfig.httpSafeOrigin + window.location.pathname + 'inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); @@ -36,6 +45,8 @@ define([ window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { SFCommonO.start({ + hash: hash, + href: href, useCreationScreen: true, messaging: true }); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index ca84bc637..538fb4019 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -30,6 +30,11 @@ define([ var password; var initialPathInDrive; + var currentPad = { + href: cfg.href || window.location.href, + hash: cfg.hash || window.location.hash + }; + nThen(function (waitFor) { // Load #2, the loading screen is up so grab whatever you need... require([ @@ -134,11 +139,12 @@ define([ }); } }), { - driveEvents: cfg.driveEvents + driveEvents: cfg.driveEvents, + currentPad: currentPad }); })); }).nThen(function (waitFor) { - if (!Utils.Hash.isValidHref(window.location.href)) { + if (!Utils.Hash.isValidHref(currentPad.href)) { waitFor.abort(); return void sframeChan.event('EV_LOADING_ERROR', 'INVALID_HASH'); } @@ -171,11 +177,12 @@ define([ }); })); } else { - var parsed = Utils.Hash.parsePadUrl(window.location.href); + var parsed = Utils.Hash.parsePadUrl(currentPad.href); var todo = function () { - secret = Utils.secret = Utils.Hash.getSecrets(parsed.type, void 0, password); + secret = Utils.secret = Utils.Hash.getSecrets(parsed.type, parsed.hash, password); Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; + /* XXX this won't happen again: we don't need to update the rendered hash if (password && !parsed.hashData.password) { var ohc = window.onhashchange; window.onhashchange = function () {}; @@ -183,6 +190,7 @@ define([ window.onhashchange = ohc; ohc({reset: true}); } + */ })); }; @@ -241,13 +249,13 @@ define([ if (parsed.type === "file") { // `isNewChannel` doesn't work for files (not a channel) // `getFileSize` is not adapted to channels because of metadata - Cryptpad.getFileSize(window.location.href, password, function (e, size) { + Cryptpad.getFileSize(currentPad.href, password, function (e, size) { next(e, size === 0); }); return; } // Not a file, so we can use `isNewChannel` - Cryptpad.isNewChannel(window.location.href, password, next); + Cryptpad.isNewChannel(currentPad.href, password, next); }); sframeChan.event("EV_PAD_PASSWORD", cfg); }; @@ -257,7 +265,60 @@ define([ var passwordCfg = { value: '' }; + + // Hidden hash: can't find the channel in our drives: abort + var noPadData = function (err) { + console.error(err); + // XXX Display error screen in inner + }; + // Hidden hash: can't find requestd edit URL in our drives: ask + var badPadData = function (cb) { + // If we requested edit but we only know view: ??? + setTimeout(function () { + cb(true); + }); // XXX ask in inner? + }; + + var newHref; nThen(function (w) { + if (!parsed.hashData.key && parsed.hashData.channel) { + Cryptpad.getPadDataFromChannel({ + channel: parsed.hashData.channel, + edit: parsed.hashData.mode === 'edit' + }, w(function (err, res) { + // Error while getting data? abort + if (err || !res || res.error) { + w.abort(); + return void noPadData(err || (!res ? 'EINVAL' : res.error)); + } + // No data found? abort + if (!Object.keys(res).length) { + w.abort(); + return void noPadData('NO_RESULT'); + } + // Data found but weaker? warn + if (parsed.hashData.mode === 'edit' && !res.href) { + return void badPadData(w(function (load) { + if (!load) { + w.abort(); + return; + } + newHref = res.roHref; + })); + } + // We have good data, keep the hash in memory + newHref = res.href; + })); + } + }).nThen(function (w) { + if (newHref) { + // Get the options (embed, present, etc.) of the hidden hash + // Use the same options in the full hash + var opts = parsed.getOptions(); + parsed = Utils.Hash.parsePadUrl(newHref); + currentPad.href = parsed.getUrl(opts); + currentPad.hash = parsed.hashData && parsed.hashData.getHash(opts); + } Cryptpad.getPadAttribute('title', w(function (err, data) { stored = (!err && typeof (data) === "string"); })); @@ -273,7 +334,7 @@ define([ if (parsed.type === "file") { // `isNewChannel` doesn't work for files (not a channel) // `getFileSize` is not adapted to channels because of metadata - Cryptpad.getFileSize(window.location.href, password, w(function (e, size) { + Cryptpad.getFileSize(currentPad.href, password, w(function (e, size) { if (size !== 0) { return void todo(); } // Wrong password or deleted file? askPassword(true, passwordCfg); @@ -281,7 +342,7 @@ define([ return; } // Not a file, so we can use `isNewChannel` - Cryptpad.isNewChannel(window.location.href, password, w(function(e, isNew) { + Cryptpad.isNewChannel(currentPad.href, password, w(function(e, isNew) { if (!isNew) { return void todo(); } if (parsed.hashData.mode === 'view' && (password || !parsed.hashData.password)) { // Error, wrong password stored, the view seed has changed with the password @@ -305,10 +366,12 @@ define([ } }).nThen(function (waitFor) { // Check if the pad exists on server - if (!window.location.hash) { isNewFile = true; return; } + if (!currentPad.hash) { isNewFile = true; return; } if (realtime) { - Cryptpad.isNewChannel(window.location.href, password, waitFor(function (e, isNew) { + // TODO we probably don't need to check again for password-protected pads + // (we use isNewChannel to test the password...) + Cryptpad.isNewChannel(currentPad.href, password, waitFor(function (e, isNew) { if (e) { return console.error(e); } isNewFile = Boolean(isNew); })); @@ -322,7 +385,7 @@ define([ readOnly = false; } Utils.crypto = Utils.Crypto.createEncryptor(Utils.secret.keys); - var parsed = Utils.Hash.parsePadUrl(window.location.href); + var parsed = Utils.Hash.parsePadUrl(currentPad.href); var burnAfterReading = parsed && parsed.hashData && parsed.hashData.ownerKey; if (!parsed.type) { throw new Error(); } var defaultTitle = Utils.UserObject.getDefaultName(parsed); @@ -342,7 +405,7 @@ define([ notifications = metaObj.user.notifications; })); if (typeof(isTemplate) === "undefined") { - Cryptpad.isTemplate(window.location.href, waitFor(function (err, t) { + Cryptpad.isTemplate(currentPad.href, waitFor(function (err, t) { if (err) { console.log(err); } isTemplate = t; })); @@ -368,7 +431,7 @@ define([ upgradeURL: Cryptpad.upgradeURL }, isNewFile: isNewFile, - isDeleted: isNewFile && window.location.hash.length > 0, + isDeleted: isNewFile && currentPad.hash.length > 0, forceCreationScreen: forceCreationScreen, password: password, channel: secret.channel, @@ -487,7 +550,7 @@ define([ }); sframeChan.on('Q_SET_LOGIN_REDIRECT', function (data, cb) { - sessionStorage.redirectTo = window.location.href; + sessionStorage.redirectTo = currentPad.href; cb(); }); @@ -570,7 +633,16 @@ define([ channel: secret.channel, path: initialPathInDrive // Where to store the pad if we don't have it in our drive }; - Cryptpad.setPadTitle(data, function (err) { + Cryptpad.setPadTitle(data, function (err, obj) { + if (!err && !(obj && obj.notStored)) { + // Pad is stored: hide the hash + var opts = parsed.getOptions(); + var hash = Utils.Hash.getHiddenHashFromKeys(parsed.type, secret, opts); + if (window.history && window.history.replaceState) { + if (!/^#/.test(hash)) { hash = '#' + hash; } + window.history.replaceState({}, window.document.title, hash); + } + } cb({error: err}); }); }); @@ -580,6 +652,9 @@ define([ }); sframeChan.on('EV_SET_HASH', function (hash) { + // In this case, we want to set the hash for the next page reload + // This hash is a category for the sidebar layout apps + // No need to store it in memory window.location.hash = hash; }); @@ -801,15 +876,19 @@ define([ // Present mode URL sframeChan.on('Q_PRESENT_URL_GET_VALUE', function (data, cb) { - var parsed = Utils.Hash.parsePadUrl(window.location.href); + var parsed = Utils.Hash.parsePadUrl(currentPad.href); cb(parsed.hashData && parsed.hashData.present); }); sframeChan.on('EV_PRESENT_URL_SET_VALUE', function (data) { - var parsed = Utils.Hash.parsePadUrl(window.location.href); - window.location.href = parsed.getUrl({ - embed: parsed.hashData.embed, - present: data - }); + // Update the rendered hash and the full hash with the "present" settings + var opts = parsed.getOptions(); + opts.present = data; + // Full hash + currentPad.href = parsed.getUrl(opts); + if (parsed.hashData) { currentPad.hash = parsed.hashData.getHash(opts); } + // Rendered (maybe hidden) hash + var hiddenParsed = Utils.Hash.parsePadUrl(window.location.href); + window.location.href = hiddenParsed.getUrl(opts); }); @@ -1011,7 +1090,7 @@ define([ }); sframeChan.on('Q_BLOB_PASSWORD_CHANGE', function (data, cb) { - data.href = data.href || window.location.href; + data.href = data.href || currentPad.href; var onPending = function (cb) { sframeChan.query('Q_BLOB_PASSWORD_CHANGE_PENDING', null, function (err, obj) { if (obj && obj.cancel) { cb(); } @@ -1027,12 +1106,12 @@ define([ }); sframeChan.on('Q_OO_PASSWORD_CHANGE', function (data, cb) { - data.href = data.href || window.location.href; + data.href = data.href || currentPad.href; Cryptpad.changeOOPassword(data, cb); }); sframeChan.on('Q_PAD_PASSWORD_CHANGE', function (data, cb) { - data.href = data.href || window.location.href; + data.href = data.href || currentPad.href; Cryptpad.changePadPassword(Cryptget, Crypto, data, cb); }); @@ -1234,7 +1313,11 @@ define([ var startRealtime = function (rtConfig) { rtConfig = rtConfig || {}; rtStarted = true; + var replaceHash = function (hash) { + // XXX Always put the full hash here. + // The pad has just been created but is not stored yet. We'll switch + // to hidden hash once the pad is stored if (window.history && window.history.replaceState) { if (!/^#/.test(hash)) { hash = '#' + hash; } window.history.replaceState({}, window.document.title, hash); @@ -1250,7 +1333,7 @@ define([ Cryptpad.padRpc.onReadyEvent.reg(function () { Cryptpad.burnPad({ password: password, - href: window.location.href, + href: currentPad.href, channel: secret.channel, ownerKey: burnAfterReading }); @@ -1265,7 +1348,7 @@ define([ readOnly: readOnly, crypto: Crypto.createEncryptor(secret.keys), onConnect: function () { - if (window.location.hash && window.location.hash !== '#') { + if (currentPad.hash && currentPad.hash !== '#') { /*window.location = parsed.getUrl({ present: parsed.hashData.present, embed: parsed.hashData.embed @@ -1278,11 +1361,11 @@ define([ }; nThen(function (waitFor) { - if (isNewFile && cfg.owned && !window.location.hash) { + if (isNewFile && cfg.owned && !currentPad.hash) { Cryptpad.getMetadata(waitFor(function (err, m) { cpNfCfg.owners = [m.priv.edPublic]; })); - } else if (isNewFile && !cfg.useCreationScreen && window.location.hash) { + } else if (isNewFile && !cfg.useCreationScreen && currentPad.hash) { console.log("new file with hash in the address bar in an app without pcs and which requires owners"); sframeChan.onReady(function () { sframeChan.query("EV_LOADING_ERROR", "DELETED"); @@ -1309,11 +1392,13 @@ define([ var ohc = window.onhashchange; window.onhashchange = function () {}; window.location.hash = newHash; + currentPad.hash = newHash; + currentPad.href = '/' + parsed.type + '/#' + newHash; window.onhashchange = ohc; ohc({reset: true}); // Update metadata values and send new metadata inside - parsed = Utils.Hash.parsePadUrl(window.location.href); + parsed = Utils.Hash.parsePadUrl(currentPad.href); defaultTitle = Utils.UserObject.getDefaultName(parsed); hashes = Utils.Hash.getHashes(secret); readOnly = false; diff --git a/www/poll/main.js b/www/poll/main.js index 2f62d3323..f2747b055 100644 --- a/www/poll/main.js +++ b/www/poll/main.js @@ -9,6 +9,7 @@ define([ var requireConfig = RequireConfig(); // Loaded in load #2 + var hash, href; nThen(function (waitFor) { DomReady.onReady(waitFor()); }).nThen(function (waitFor) { @@ -19,6 +20,14 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; + + // Hidden hash + hash = window.location.hash; + href = window.location.href; + if (window.history && window.history.replaceState) { + window.history.replaceState({}, window.document.title, '#'); + } + document.getElementById('sbox-iframe').setAttribute('src', ApiConfig.httpSafeOrigin + '/poll/inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); @@ -37,6 +46,8 @@ define([ window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { SFCommonO.start({ + hash: hash, + href: href, useCreationScreen: true, messaging: true }); From a8e62505763f71dda0852b044240ffc17c04523a Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Jan 2020 13:34:41 +0100 Subject: [PATCH 03/18] Hidden hash for shared folders and team invitation --- www/common/sframe-common-outer.js | 2 +- www/drive/main.js | 24 +++++++++++++++++++----- www/teams/main.js | 12 ++++++++++-- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 538fb4019..039bca5e7 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -144,7 +144,7 @@ define([ }); })); }).nThen(function (waitFor) { - if (!Utils.Hash.isValidHref(currentPad.href)) { + if (!Utils.Hash.isValidHref(window.location.href)) { waitFor.abort(); return void sframeChan.event('EV_LOADING_ERROR', 'INVALID_HASH'); } diff --git a/www/drive/main.js b/www/drive/main.js index bd2e506d4..0a7f78049 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -9,6 +9,7 @@ define([ var requireConfig = RequireConfig(); // Loaded in load #2 + var hash, href; nThen(function (waitFor) { DomReady.onReady(waitFor()); }).nThen(function (waitFor) { @@ -19,6 +20,14 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; + + // Hidden hash + hash = window.location.hash; + href = window.location.href; + if (window.history && window.history.replaceState) { + window.history.replaceState({}, window.document.title, '#'); + } + document.getElementById('sbox-iframe').setAttribute('src', ApiConfig.httpSafeOrigin + '/drive/inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); @@ -37,19 +46,19 @@ define([ window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { var afterSecrets = function (Cryptpad, Utils, secret, cb) { - var hash = window.location.hash.slice(1); - if (hash && Utils.LocalStore.isLoggedIn()) { + var _hash = hash.slice(1); + if (_hash && Utils.LocalStore.isLoggedIn()) { // Add a shared folder! Cryptpad.addSharedFolder(null, secret, function (id) { window.CryptPad_newSharedFolder = id; cb(); }); return; - } else if (hash) { + } else if (_hash) { var id = Utils.Util.createRandomInteger(); window.CryptPad_newSharedFolder = id; var data = { - href: Utils.Hash.getRelativeHref(window.location.href), + href: Utils.Hash.getRelativeHref(href), password: secret.password }; return void Cryptpad.loadSharedFolder(id, data, cb); @@ -84,12 +93,15 @@ define([ }); sframeChan.on('EV_DRIVE_SET_HASH', function (hash) { // Update the hash in the address bar + // XXX Hidden hash: don't put the shared folder href in the address bar + /* if (!Utils.LocalStore.isLoggedIn()) { return; } var ohc = window.onhashchange; window.onhashchange = function () {}; window.location.hash = hash || ''; window.onhashchange = ohc; ohc({reset:true}); + */ }); Cryptpad.onNetworkDisconnect.reg(function () { sframeChan.event('EV_NETWORK_DISCONNECT'); @@ -109,9 +121,11 @@ define([ }; var addData = function (meta) { if (!window.CryptPad_newSharedFolder) { return; } - meta.anonSFHref = window.location.href; + meta.anonSFHref = href; }; SFCommonO.start({ + hash: hash, + href: href, afterSecrets: afterSecrets, noHash: true, noRealtime: true, diff --git a/www/teams/main.js b/www/teams/main.js index 559e90c7c..36c4e4f66 100644 --- a/www/teams/main.js +++ b/www/teams/main.js @@ -9,6 +9,7 @@ define([ var requireConfig = RequireConfig(); // Loaded in load #2 + var hash, href; nThen(function (waitFor) { DomReady.onReady(waitFor()); }).nThen(function (waitFor) { @@ -19,6 +20,14 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; + + // Hidden hash + hash = window.location.hash; + href = window.location.href; + if (window.history && window.history.replaceState) { + window.history.replaceState({}, window.document.title, '#'); + } + document.getElementById('sbox-iframe').setAttribute('src', ApiConfig.httpSafeOrigin + '/teams/inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); @@ -37,7 +46,6 @@ define([ window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { var teamId; - var hash = window.location.hash.slice(1); var addRpc = function (sframeChan, Cryptpad) { sframeChan.on('Q_SET_TEAM', function (data, cb) { teamId = data; @@ -95,7 +103,7 @@ define([ }; var addData = function (meta) { if (!hash) { return; } - meta.teamInviteHash = hash; + meta.teamInviteHash = hash.slice(1); }; SFCommonO.start({ getSecrets: getSecrets, From 2c1e26cb527013256689db44f0e83cf5e64377bf Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Jan 2020 13:37:52 +0100 Subject: [PATCH 04/18] Remove # symbol when no hash --- www/common/onlyoffice/main.js | 2 +- www/common/sframe-app-outer.js | 2 +- www/drive/main.js | 2 +- www/poll/main.js | 2 +- www/teams/main.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/www/common/onlyoffice/main.js b/www/common/onlyoffice/main.js index 600901872..7007bd360 100644 --- a/www/common/onlyoffice/main.js +++ b/www/common/onlyoffice/main.js @@ -24,7 +24,7 @@ define([ // Hidden hash hash = window.location.hash; href = window.location.href; - if (window.history && window.history.replaceState) { + if (window.history && window.history.replaceState && hash) { window.history.replaceState({}, window.document.title, '#'); } document.getElementById('sbox-iframe').setAttribute('src', diff --git a/www/common/sframe-app-outer.js b/www/common/sframe-app-outer.js index 563430c42..d85266ca7 100644 --- a/www/common/sframe-app-outer.js +++ b/www/common/sframe-app-outer.js @@ -23,7 +23,7 @@ define([ // Hidden hash hash = window.location.hash; href = window.location.href; - if (window.history && window.history.replaceState) { + if (window.history && window.history.replaceState && hash) { window.history.replaceState({}, window.document.title, '#'); } diff --git a/www/drive/main.js b/www/drive/main.js index 0a7f78049..00f64c2c3 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -24,7 +24,7 @@ define([ // Hidden hash hash = window.location.hash; href = window.location.href; - if (window.history && window.history.replaceState) { + if (window.history && window.history.replaceState && hash) { window.history.replaceState({}, window.document.title, '#'); } diff --git a/www/poll/main.js b/www/poll/main.js index f2747b055..d1bdca24d 100644 --- a/www/poll/main.js +++ b/www/poll/main.js @@ -24,7 +24,7 @@ define([ // Hidden hash hash = window.location.hash; href = window.location.href; - if (window.history && window.history.replaceState) { + if (window.history && window.history.replaceState && hash) { window.history.replaceState({}, window.document.title, '#'); } diff --git a/www/teams/main.js b/www/teams/main.js index 36c4e4f66..cfaae69a2 100644 --- a/www/teams/main.js +++ b/www/teams/main.js @@ -24,7 +24,7 @@ define([ // Hidden hash hash = window.location.hash; href = window.location.href; - if (window.history && window.history.replaceState) { + if (window.history && window.history.replaceState && hash) { window.history.replaceState({}, window.document.title, '#'); } From 7b9f86157e4decf1e2e39f92ce7991edc4b8e105 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Jan 2020 13:53:54 +0100 Subject: [PATCH 05/18] Use version 3 for hidden hashes --- www/common/common-hash.js | 89 ++++++++++++++----------------- www/common/sframe-common-outer.js | 2 +- 2 files changed, 42 insertions(+), 49 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index b92aea475..7869662d3 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -61,9 +61,9 @@ var factory = function (Util, Crypto, Nacl) { } }; Hash.getHiddenHashFromKeys = function (type, secret, opts) { - var mode = (secret.keys && secret.keys.editKeyStr) ? 'edit' : 'view'; + var mode = ((secret.keys && secret.keys.editKeyStr) || secret.key) ? 'edit' : 'view'; var pass = secret.password ? 'p/' : ''; - var hash = '/2/' + secret.type + '/' + mode + '/' + secret.channel + '/' + pass; + var hash = '/3/' + type + '/' + mode + '/' + secret.channel + '/' + pass; var href = '/' + type + '/#' + hash; var parsed = Hash.parsePadUrl(href); if (parsed.hashData && parsed.hashData.getHash) { @@ -172,12 +172,19 @@ Version 1 }; var parseTypeHash = Hash.parseTypeHash = function (type, hash) { if (!hash) { return; } - var options; + var options = []; var parsed = {}; var hashArr = fixDuplicateSlashes(hash).split('/'); 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 + }; + }; if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0 // Old hash parsed.channel = hash.slice(0, 32); @@ -185,6 +192,24 @@ Version 1 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) { hash += 'p/'; } + if (opts.embed) { hash += 'embed/'; } + if (opts.present) { hash += 'present/'; } + return hash; + }; + var addOptions = function () { + parsed.password = options.indexOf('p') !== -1; + parsed.present = options.indexOf('present') !== -1; + parsed.embed = options.indexOf('embed') !== -1; + parsed.ownerKey = getOwnerKey(options); + }; + if (hashArr[1] && hashArr[1] === '1') { // Version 1 parsed.version = 1; parsed.mode = hashArr[2]; @@ -192,61 +217,30 @@ Version 1 parsed.key = Crypto.b64AddSlashes(hashArr[4]); options = hashArr.slice(5); - parsed.present = options.indexOf('present') !== -1; - parsed.embed = options.indexOf('embed') !== -1; - parsed.ownerKey = getOwnerKey(options); + addOptions(); - 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 (opts.embed) { hash += 'embed/'; } - if (opts.present) { hash += 'present/'; } - return hash; - }; - parsed.getOptions = function () { - return { - embed: parsed.embed, - present: parsed.present, - ownerKey: parsed.ownerKey - }; - }; 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]; - // Check if the key is a channel ID - if (/^[a-f0-9]{32,34}$/.test(hashArr[4])) { - parsed.channel = hashArr[4]; - } else { - 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); - parsed.password = options.indexOf('p') !== -1; - parsed.present = options.indexOf('present') !== -1; - parsed.embed = options.indexOf('embed') !== -1; - parsed.ownerKey = getOwnerKey(options); + addOptions(); - 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) { hash += 'p/'; } - if (opts.embed) { hash += 'embed/'; } - if (opts.present) { hash += 'present/'; } - return hash; - }; - parsed.getOptions = function () { - return { - embed: parsed.embed, - present: parsed.present, - ownerKey: parsed.ownerKey - }; - }; return parsed; } return parsed; @@ -536,7 +530,6 @@ Version 1 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; } - if (parsed.hashData.channel && !/^[a-f0-9]{32,34}$/.test(parsed.hashData.channel)) { return; } } } return true; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 039bca5e7..a604cad38 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -30,7 +30,7 @@ define([ var password; var initialPathInDrive; - var currentPad = { + var currentPad = window.CryptPad_location = { href: cfg.href || window.location.href, hash: cfg.hash || window.location.hash }; From 7a02b074b734d7494ffe3ed3150a5c2c3290d764 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Jan 2020 15:45:57 +0100 Subject: [PATCH 06/18] Hidden hash for files --- www/common/common-hash.js | 78 +++++++++++++++++++------------ www/common/cryptpad-common.js | 6 +-- www/common/outer/async-store.js | 3 +- www/common/sframe-common-outer.js | 3 +- www/file/main.js | 15 +++++- 5 files changed, 68 insertions(+), 37 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 7869662d3..7eb3ae6fa 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -60,10 +60,14 @@ var factory = function (Util, Crypto, Nacl) { return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass; } }; + Hash.getHiddenHashFromKeys = function (type, secret, opts) { - var mode = ((secret.keys && secret.keys.editKeyStr) || secret.key) ? 'edit' : 'view'; + var mode = ((secret.keys && secret.keys.editKeyStr) || secret.key) ? 'edit/' : 'view/'; var pass = secret.password ? 'p/' : ''; - var hash = '/3/' + type + '/' + mode + '/' + secret.channel + '/' + pass; + + if (secret.keys && secret.keys.fileKeyStr) { mode = ''; } + + var hash = '/3/' + type + '/' + mode + secret.channel + '/' + pass; var href = '/' + type + '/#' + hash; var parsed = Hash.parsePadUrl(href); if (parsed.hashData && parsed.hashData.getHash) { @@ -175,6 +179,14 @@ Version 1 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.ownerKey = getOwnerKey(options); + }; + if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) { parsed.type = 'pad'; parsed.getHash = function () { return hash; }; @@ -203,12 +215,6 @@ Version 1 if (opts.present) { hash += 'present/'; } return hash; }; - var addOptions = function () { - parsed.password = options.indexOf('p') !== -1; - parsed.present = options.indexOf('present') !== -1; - parsed.embed = options.indexOf('embed') !== -1; - parsed.ownerKey = getOwnerKey(options); - }; if (hashArr[1] && hashArr[1] === '1') { // Version 1 parsed.version = 1; @@ -248,41 +254,53 @@ Version 1 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 + }; + }; + + 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) { hash += 'p/'; } + if (opts.embed) { hash += 'embed/'; } + if (opts.present) { hash += 'present/'; } + 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); - parsed.ownerKey = getOwnerKey(options); + 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); - parsed.password = options.indexOf('p') !== -1; - parsed.present = options.indexOf('present') !== -1; - parsed.embed = options.indexOf('embed') !== -1; - parsed.ownerKey = getOwnerKey(options); - - 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) { hash += 'p/'; } - if (opts.embed) { hash += 'embed/'; } - if (opts.present) { hash += 'present/'; } - return hash; - }; - parsed.getOptions = function () { - return { - embed: parsed.embed, - present: parsed.present, - ownerKey: parsed.ownerKey - }; - }; + 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; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 0be93e14c..1f612bc84 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -51,7 +51,7 @@ define([ // Store the href in memory // This is a placeholder value overriden in common.ready from sframe-common-outer - var currentPad = { + var currentPad = common.currentPad = { href: window.location.href }; @@ -763,7 +763,7 @@ define([ }; // Get data about a given channel: use with hidden hashes common.getPadDataFromChannel = function (obj, cb) { - if (!obj || !obj.channel || !obj.edit) { return void cb('EINVAL'); } + if (!obj || !obj.channel) { return void cb('EINVAL'); } postMessage("GET_PAD_DATA_FROM_CHANNEL", obj, function (data) { cb(void 0, data); }); @@ -1795,7 +1795,7 @@ define([ rdyCfg = rdyCfg || {}; if (rdyCfg.currentPad) { - currentPad = rdyCfg.currentPad; + currentPad = common.currentPad = rdyCfg.currentPad; } if (initialized) { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 4b2fda935..a06f593e7 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1208,6 +1208,7 @@ define([ Store.getPadDataFromChannel = function (clientId, obj, cb) { var channel = obj.channel; var edit = obj.edit; + var isFile = obj.file; var res; var viewRes; getAllStores().some(function (s) { @@ -1217,7 +1218,7 @@ define([ if (!pad || !pad.data) { return; } var data = pad.data; // We've found a match: return the value and stop the loops - if ((edit && data.href) || (!edit && data.roHref)) { + if ((edit && data.href) || (!edit && data.roHref) || isFile) { res = data; return true; } diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index a604cad38..f9a2baa23 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -284,7 +284,8 @@ define([ if (!parsed.hashData.key && parsed.hashData.channel) { Cryptpad.getPadDataFromChannel({ channel: parsed.hashData.channel, - edit: parsed.hashData.mode === 'edit' + edit: parsed.hashData.mode === 'edit', + file: parsed.hashData.type === 'file' }, w(function (err, res) { // Error while getting data? abort if (err || !res || res.error) { diff --git a/www/file/main.js b/www/file/main.js index e59957299..8dc1c8eef 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -9,6 +9,7 @@ define([ var requireConfig = RequireConfig(); // Loaded in load #2 + var hash, href; nThen(function (waitFor) { DomReady.onReady(waitFor()); }).nThen(function (waitFor) { @@ -19,6 +20,14 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; + + // Hidden hash + hash = window.location.hash; + href = window.location.href; + if (window.history && window.history.replaceState && hash) { + window.history.replaceState({}, window.document.title, '#'); + } + document.getElementById('sbox-iframe').setAttribute('src', ApiConfig.httpSafeOrigin + '/file/inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); @@ -36,10 +45,12 @@ define([ }; window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { - var addData = function (meta) { - meta.filehash = window.location.hash; + var addData = function (meta, Cryptpad) { + meta.filehash = Cryptpad.currentPad.hash; }; SFCommonO.start({ + hash: hash, + href: href, noRealtime: true, addData: addData }); From deddc8027071711fa59a36e045c7c5cb56e91cdf Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Jan 2020 15:59:24 +0100 Subject: [PATCH 07/18] lnit compliance --- www/drive/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/drive/main.js b/www/drive/main.js index 00f64c2c3..64e990979 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -91,7 +91,7 @@ define([ cb(obj); }); }); - sframeChan.on('EV_DRIVE_SET_HASH', function (hash) { + sframeChan.on('EV_DRIVE_SET_HASH', function (/*hash*/) { // Update the hash in the address bar // XXX Hidden hash: don't put the shared folder href in the address bar /* From 83c35543b955f9abc039c524b3524f1c14e0fb54 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Jan 2020 18:09:02 +0100 Subject: [PATCH 08/18] Keep the hash in the URL while the pad is loading --- www/common/sframe-common-outer.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index f9a2baa23..c92bc5121 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -142,6 +142,12 @@ define([ driveEvents: cfg.driveEvents, currentPad: currentPad }); + + if (window.history && window.history.replaceState && currentPad.hash) { + var nHash = currentPad.hash; + if (!/^#/.test(nHash)) { nHash = '#' + nHash; } + window.history.replaceState({}, window.document.title, nHash); + } })); }).nThen(function (waitFor) { if (!Utils.Hash.isValidHref(window.location.href)) { From 718610b6dbd869b0ea76f62c50d45482f111a266 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Jan 2020 18:09:35 +0100 Subject: [PATCH 09/18] Use the hidden hash when opening a pad from the drive --- www/common/common-hash.js | 15 +++++++++++---- www/common/drive-ui.js | 28 ++++++++++++++-------------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 7eb3ae6fa..b9615ff1c 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -68,10 +68,9 @@ var factory = function (Util, Crypto, Nacl) { if (secret.keys && secret.keys.fileKeyStr) { mode = ''; } var hash = '/3/' + type + '/' + mode + secret.channel + '/' + pass; - var href = '/' + type + '/#' + hash; - var parsed = Hash.parsePadUrl(href); - if (parsed.hashData && parsed.hashData.getHash) { - return parsed.hashData.getHash(opts || {}); + var hashData = Hash.parseTypeHash(type, hash); + if (hashData && hashData.getHash) { + return hashData.getHash(opts || {}); } return hash; }; @@ -380,6 +379,14 @@ Version 1 return ret; }; + Hash.hashToHref = function (hash, type) { + return '/' + type + '/#' + hash; + }; + Hash.hrefToHref = function (href) { + var parsed = parsedPadUrl(href); + return parsed.hash; + }; + Hash.getRelativeHref = function (href) { if (!href) { return; } if (href.indexOf('#') === -1) { return; } diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index bc0fda97e..4abb98510 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -1029,15 +1029,18 @@ define([ return ret; }; - var openFile = function (el, href) { - if (!href) { - var data = manager.getFileData(el); - if (!data || (!data.href && !data.roHref)) { - return void logError("Missing data for the file", el, data); - } - href = data.href || data.roHref; + var openFile = function (el, isRo) { + var data = manager.getFileData(el); + if (!data || (!data.href && !data.roHref)) { + return void logError("Missing data for the file", el, data); } - window.open(APP.origin + href); + var href = data.href || data.roHref; + var parsed = Hash.parsePadUrl(href); + var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password); + var hash = Hash.getHiddenHashFromKeys(parsed.type, secret); + var hiddenHref = Hash.hashToHref(hash, parsed.type); + // XXX hidden hash: use settings + window.open(APP.origin + hiddenHref); }; var refresh = APP.refresh = function () { @@ -3034,7 +3037,7 @@ define([ $icon.append(getFileIcon(r.id)); $type.text(Messages.type[parsed.type] || parsed.type); $title.click(function () { - openFile(null, r.data.href); + openFile(r.id); }); $atimeName.text(Messages.fm_lastAccess); $atime.text(new Date(r.data.atime).toLocaleString()); @@ -3944,15 +3947,12 @@ define([ // ANON_SHARED_FOLDER el = manager.find(paths[0].path.slice(1), APP.newSharedFolder); } - var href; if (manager.isPathIn(p.path, [FILES_DATA])) { - href = el.roHref; + el = p.path[1]; } else { if (!el || manager.isFolder(el)) { return; } - var data = manager.getFileData(el); - href = data.roHref; } - openFile(null, href); + openFile(el, true); }); } else if ($this.hasClass('cp-app-drive-context-openincode')) { From 50b897ee2e590ab506042e421052861d0ff55c24 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Jan 2020 18:51:16 +0100 Subject: [PATCH 10/18] Hide the hash with autostore popup + fix anon shared folders --- www/common/sframe-common-outer.js | 13 ++++++++++++- www/drive/main.js | 12 +++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index c92bc5121..ad67db8ec 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -642,7 +642,8 @@ define([ }; Cryptpad.setPadTitle(data, function (err, obj) { if (!err && !(obj && obj.notStored)) { - // Pad is stored: hide the hash + // No error and the pad was correctly stored + // hide the hash var opts = parsed.getOptions(); var hash = Utils.Hash.getHiddenHashFromKeys(parsed.type, secret, opts); if (window.history && window.history.replaceState) { @@ -677,6 +678,16 @@ define([ forceSave: true }; Cryptpad.setPadTitle(data, function (err) { + if (!err && !(obj && obj.notStored)) { + // No error and the pad was correctly stored + // hide the hash + var opts = parsed.getOptions(); + var hash = Utils.Hash.getHiddenHashFromKeys(parsed.type, secret, opts); + if (window.history && window.history.replaceState) { + if (!/^#/.test(hash)) { hash = '#' + hash; } + window.history.replaceState({}, window.document.title, hash); + } + } cb({error: err}); }); }); diff --git a/www/drive/main.js b/www/drive/main.js index 64e990979..6e22e6b27 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -51,6 +51,12 @@ define([ // Add a shared folder! Cryptpad.addSharedFolder(null, secret, function (id) { window.CryptPad_newSharedFolder = id; + + // Clear the hash now that the secrets have been generated + if (window.history && window.history.replaceState && hash) { + window.history.replaceState({}, window.document.title, '#'); + } + cb(); }); return; @@ -58,7 +64,7 @@ define([ var id = Utils.Util.createRandomInteger(); window.CryptPad_newSharedFolder = id; var data = { - href: Utils.Hash.getRelativeHref(href), + href: Utils.Hash.getRelativeHref(Cryptpad.currentPad.href), password: secret.password }; return void Cryptpad.loadSharedFolder(id, data, cb); @@ -119,9 +125,9 @@ define([ sframeChan.event('EV_DRIVE_REMOVE', data); }); }; - var addData = function (meta) { + var addData = function (meta, Cryptpad) { if (!window.CryptPad_newSharedFolder) { return; } - meta.anonSFHref = href; + meta.anonSFHref = Cryptpad.currentPad.href; }; SFCommonO.start({ hash: hash, From ea65647d44d02974729b803e82ed22e4204627c2 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Jan 2020 18:55:03 +0100 Subject: [PATCH 11/18] lint compliance --- www/common/common-hash.js | 2 +- www/common/drive-ui.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index b9615ff1c..1d05710a8 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -383,7 +383,7 @@ Version 1 return '/' + type + '/#' + hash; }; Hash.hrefToHref = function (href) { - var parsed = parsedPadUrl(href); + var parsed = Hash.parsePadUrl(href); return parsed.hash; }; diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 4abb98510..412c0be41 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -1037,6 +1037,10 @@ define([ var href = data.href || data.roHref; var parsed = Hash.parsePadUrl(href); var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password); + if (isRo && secret.keys && secret.keys.editKeyStr) { + delete secret.keys.editKeyStr; + delete secret.key; + } var hash = Hash.getHiddenHashFromKeys(parsed.type, secret); var hiddenHref = Hash.hashToHref(hash, parsed.type); // XXX hidden hash: use settings From 0237bb2867a056d1686f4f9c5993a53688bea6f2 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 28 Jan 2020 10:46:26 +0100 Subject: [PATCH 12/18] Fix read-only pads --- www/common/sframe-common-outer.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index ad67db8ec..0197d68ee 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -288,9 +288,10 @@ define([ var newHref; nThen(function (w) { if (!parsed.hashData.key && parsed.hashData.channel) { + var edit = parsed.hashData.mode === 'edit'; Cryptpad.getPadDataFromChannel({ channel: parsed.hashData.channel, - edit: parsed.hashData.mode === 'edit', + edit: edit, file: parsed.hashData.type === 'file' }, w(function (err, res) { // Error while getting data? abort @@ -304,7 +305,7 @@ define([ return void noPadData('NO_RESULT'); } // Data found but weaker? warn - if (parsed.hashData.mode === 'edit' && !res.href) { + if (edit && !res.href) { return void badPadData(w(function (load) { if (!load) { w.abort(); @@ -314,7 +315,7 @@ define([ })); } // We have good data, keep the hash in memory - newHref = res.href; + newHref = edit ? res.href : (res.roHref || res.href); })); } }).nThen(function (w) { From 6183401a6f810bd62d9defc4282f1e6ef28ab7f5 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 28 Jan 2020 11:31:03 +0100 Subject: [PATCH 13/18] Add settings to continue using unsafe links --- www/common/common-interface.js | 31 +++++++++++++++++ www/common/drive-ui.js | 10 ++++-- www/common/sframe-common-outer.js | 8 +++-- www/settings/inner.js | 57 +++++++++++++++++++++++++++++-- 4 files changed, 99 insertions(+), 7 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 9cc1c1efb..ba5ca378b 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -1104,5 +1104,36 @@ define([ }; }; + UI.makeSpinner = function ($container) { + var $ok = $('', {'class': 'fa fa-check', title: Messages.saved}).hide(); + var $spinner = $('', {'class': 'fa fa-spinner fa-pulse'}).hide(); + + var spin = function () { + $ok.hide(); + $spinner.show(); + }; + var hide = function () { + $ok.hide(); + $spinner.hide(); + }; + var done = function () { + $ok.show(); + $spinner.hide(); + }; + + if ($container && $container.append) { + $container.append($ok); + $container.append($spinner); + } + + return { + ok: $ok[0], + spinner: $spinner[0], + spin: spin, + hide: hide, + done: done + } + }; + return UI; }); diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 412c0be41..b0396773c 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -1034,7 +1034,14 @@ define([ if (!data || (!data.href && !data.roHref)) { return void logError("Missing data for the file", el, data); } - var href = data.href || data.roHref; + var href = isRo ? data.roHref : (data.href || data.roHref); + var priv = metadataMgr.getPrivateData(); + var useUnsafe = Util.find(priv, ['settings', 'security', 'unsafeLinks']); + if (useUnsafe) { + return void window.open(APP.origin + href); + } + + // Get hidden hash var parsed = Hash.parsePadUrl(href); var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password); if (isRo && secret.keys && secret.keys.editKeyStr) { @@ -1043,7 +1050,6 @@ define([ } var hash = Hash.getHiddenHashFromKeys(parsed.type, secret); var hiddenHref = Hash.hashToHref(hash, parsed.type); - // XXX hidden hash: use settings window.open(APP.origin + hiddenHref); }; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 0197d68ee..e2472928e 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -398,6 +398,7 @@ define([ if (!parsed.type) { throw new Error(); } var defaultTitle = Utils.UserObject.getDefaultName(parsed); var edPublic, curvePublic, notifications, isTemplate; + var settings = {}; var forceCreationScreen = cfg.useCreationScreen && sessionStorage[Utils.Constants.displayPadCreationScreen]; delete sessionStorage[Utils.Constants.displayPadCreationScreen]; @@ -411,6 +412,7 @@ define([ edPublic = metaObj.priv.edPublic; // needed to create an owned pad curvePublic = metaObj.user.curvePublic; notifications = metaObj.user.notifications; + settings = metaObj.priv.settings; })); if (typeof(isTemplate) === "undefined") { Cryptpad.isTemplate(currentPad.href, waitFor(function (err, t) { @@ -647,7 +649,8 @@ define([ // hide the hash var opts = parsed.getOptions(); var hash = Utils.Hash.getHiddenHashFromKeys(parsed.type, secret, opts); - if (window.history && window.history.replaceState) { + var useUnsafe = Utils.Util.find(settings, ['security', 'unsafeLinks']); + if (!useUnsafe && window.history && window.history.replaceState) { if (!/^#/.test(hash)) { hash = '#' + hash; } window.history.replaceState({}, window.document.title, hash); } @@ -684,7 +687,8 @@ define([ // hide the hash var opts = parsed.getOptions(); var hash = Utils.Hash.getHiddenHashFromKeys(parsed.type, secret, opts); - if (window.history && window.history.replaceState) { + var useUnsafe = Utils.Util.find(settings, ['security', 'unsafeLinks']); + if (!useUnsafe && window.history && window.history.replaceState) { if (!/^#/.test(hash)) { hash = '#' + hash; } window.history.replaceState({}, window.document.title, hash); } diff --git a/www/settings/inner.js b/www/settings/inner.js index 7c9dd0630..b3aa9bb0b 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -53,13 +53,15 @@ define([ 'cp-settings-language-selector', 'cp-settings-resettips', 'cp-settings-logout-everywhere', - 'cp-settings-autostore', 'cp-settings-userfeedback', 'cp-settings-change-password', 'cp-settings-migrate', - 'cp-settings-backup', 'cp-settings-delete' ], + 'security': [ // XXX + 'cp-settings-autostore', + 'cp-settings-safe-links', + ], 'creation': [ 'cp-settings-creation-owned', 'cp-settings-creation-expire', @@ -115,6 +117,24 @@ define([ var create = {}; + var makeBlock = function (key, getter, full) { + var safeKey = key.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); }); + + create[key] = function () { + var $div = $('
', {'class': 'cp-settings-' + key + ' cp-sidebarlayout-element'}); + if (full) { + $('