diff --git a/www/common/common-hash.js b/www/common/common-hash.js index a2e96e2fc..fb5f0d99c 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -32,9 +32,58 @@ define([ return '/1/view/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.viewKeyStr)+'/'; }; var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) { - return '/2/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/'; + return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/'; }; + var fixDuplicateSlashes = function (s) { + return s.replace(/\/+/g, '/'); + }; + + var parseTypeHash = Hash.parseTypeHash = function (type, hash) { + if (!hash) { return; } + var parsed = {} + var hashArr = fixDuplicateSlashes(hash).split('/'); + if (['media', 'file', 'user'].indexOf(type) === -1) { + parsed.type = 'pad'; + if (hash.slice(0,1) !== '/' && hash.length >= 56) { + // Old hash + parsed.channel = hash.slice(0, 32); + parsed.key = hash.slice(32); + parsed.version = 0; + return parsed; + } + if (hashArr[1] && hashArr[1] === '1') { + parsed.version = 1; + parsed.mode = hashArr[2]; + parsed.channel = hashArr[3]; + parsed.key = hashArr[4].replace(/-/g, '/'); + parsed.present = typeof(hashArr[5]) === "string" && hashArr[5] === 'present'; + return parsed; + } + return parsed; + } + if (['media', 'file'].indexOf(type) !== -1) { + parsed.type = 'file'; + if (hashArr[1] && hashArr[1] === '1') { + parsed.version = 1; + parsed.channel = hashArr[2].replace(/-/g, '/'); + parsed.key = hashArr[3].replace(/-/g, '/'); + return parsed; + } + return parsed; + } + if (['user'].indexOf(type) !== -1) { + parsed.type = 'user'; + if (hashArr[1] && hashArr[1] === '1') { + parsed.version = 1; + parsed.user = hashArr[2]; + parsed.pubkey = hashArr[3].replace(/-/g, '/'); + return parsed; + } + return parsed; + } + return; + }; var parsePadUrl = Hash.parsePadUrl = function (href) { var patt = /^https*:\/\/([^\/]*)\/(.*?)\//i; @@ -43,10 +92,13 @@ define([ if (!href) { return ret; } if (href.slice(-1) !== '/') { href += '/'; } + + if (!/^https*:\/\//.test(href)) { var idx = href.indexOf('/#'); ret.type = href.slice(1, idx); ret.hash = href.slice(idx + 2); + ret.hashData = parseTypeHash(ret.type, ret.hash); return ret; } @@ -56,6 +108,7 @@ define([ return ''; }); ret.hash = hash.replace(/#/g, ''); + ret.hashData = parseTypeHash(ret.type, ret.hash); return ret; }; @@ -66,16 +119,12 @@ define([ return '/' + parsed.type + '/#' + parsed.hash; }; - var fixDuplicateSlashes = function (s) { - return s.replace(/\/+/g, '/'); - }; - /* * Returns all needed keys for a realtime channel * - no argument: use the URL hash or create one if it doesn't exist * - secretHash provided: use secretHash to find the keys */ - Hash.getSecrets = function (secretHash) { + Hash.getSecrets = function (type, secretHash) { var secret = {}; var generate = function () { secret.keys = Crypto.createEditCryptor(); @@ -85,50 +134,55 @@ define([ generate(); return secret; } else { - var hash = secretHash || window.location.hash.slice(1); + var parsed; + var hash; + if (secretHash) { + if (!type) { throw new Error("getSecrets with a hash requires a type parameter"); } + parsed = parseTypeHash(type, secretHash); + hash = secretHash; + } else { + var pHref = parsePadUrl(window.location.href); + parsed = pHref.hashData; + hash = pHref.hash; + } + //var parsed = parsePadUrl(window.location.href); + //var hash = secretHash || window.location.hash.slice(1); + console.log(hash, parsed); if (hash.length === 0) { generate(); return secret; } // old hash system : #{hexChanKey}{cryptKey} // new hash system : #/{hashVersion}/{b64ChanKey}/{cryptKey} - if (hash.slice(0,1) !== '/' && hash.length >= 56) { + if (parsed.version === 0) { // Old hash secret.channel = hash.slice(0, 32); secret.key = hash.slice(32); } - else { + else if (parsed.version === 1) { // New hash - var hashArray = fixDuplicateSlashes(hash).split('/'); - if (hashArray.length < 4) { - Hash.alert("Unable to parse the key"); - throw new Error("Unable to parse the key"); - } - var version = hashArray[1]; - if (version === "1") { - var mode = hashArray[2]; - if (mode === 'edit') { - secret.channel = base64ToHex(hashArray[3]); - var keys = Crypto.createEditCryptor(hashArray[4].replace(/-/g, '/')); - secret.keys = keys; - secret.key = keys.editKeyStr; + if (parsed.type === "pad") { + secret.channel = base64ToHex(parsed.channel); + if (parsed.mode === 'edit') { + console.log(parsed.key); + secret.keys = Crypto.createEditCryptor(parsed.key); + secret.key = secret.keys.editKeyStr; if (secret.channel.length !== 32 || secret.key.length !== 24) { Hash.alert("The channel key and/or the encryption key is invalid"); throw new Error("The channel key and/or the encryption key is invalid"); } } - else if (mode === 'view') { - secret.channel = base64ToHex(hashArray[3]); - secret.keys = Crypto.createViewCryptor(hashArray[4].replace(/-/g, '/')); + else if (parsed.mode === 'view') { + secret.keys = Crypto.createViewCryptor(parsed.key); if (secret.channel.length !== 32) { Hash.alert("The channel key is invalid"); throw new Error("The channel key is invalid"); } } - } else if (version === "2") { + } else if (parsed.type === "file") { // version 2 hashes are to be used for encrypted blobs - secret.channel = hashArray[2].replace(/-/g, '/'); - secret.keys = { fileKeyStr: hashArray[3].replace(/-/g, '/') }; + secret.channel = parsed.channel; + secret.keys = { fileKeyStr: parsed.key }; } } } @@ -175,6 +229,7 @@ Version 2 /file/#/2/K6xWU-LT9BJHCQcDCT-DcQ/ajExFODrFH4lVBwxxsrOKw/image-png */ var parseHash = Hash.parseHash = function (hash) { + throw new Error('parseHash deprecated'); var parsed = {}; if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Old hash @@ -211,9 +266,13 @@ Version 2 var p = parsePadUrl(pad.href); if (p.type !== parsed.type) { return; } // Not the same type if (p.hash === parsed.hash) { return; } // Same hash, not stronger - var pHash = parseHash(p.hash); - var parsedHash = parseHash(parsed.hash); + var pHash = p.hashData; + var parsedHash = parsed.hashData; if (!parsedHash || !pHash) { return; } + + // We don't have stronger/weaker versions of files or users + if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; } + if (pHash.version !== parsedHash.version) { return; } if (pHash.channel !== parsedHash.channel) { return; } if (pHash.mode === 'view' && parsedHash.mode === 'edit') { @@ -233,9 +292,13 @@ Version 2 var p = parsePadUrl(pad.href); if (p.type !== parsed.type) { return; } // Not the same type if (p.hash === parsed.hash) { return; } // Same hash, not stronger - var pHash = parseHash(p.hash); - var parsedHash = parseHash(parsed.hash); + var pHash = p.hashData; + var parsedHash = parsed.hashData; if (!parsedHash || !pHash) { return; } + + // We don't have stronger/weaker versions of files or users + if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; } + if (pHash.version !== parsedHash.version) { return; } if (pHash.channel !== parsedHash.channel) { return; } if (pHash.mode === 'edit' && parsedHash.mode === 'view') { @@ -254,8 +317,7 @@ Version 2 var parsed = Hash.parsePadUrl(href); if (!parsed || !parsed.hash) { return; } - parsed = Hash.parseHash(parsed.hash); - + parsed = parsed.hashData; if (parsed.version === 0) { return parsed.channel; } else if (parsed.version !== 1 && parsed.version !== 2) { diff --git a/www/common/common-history.js b/www/common/common-history.js index 8597598c1..48b210eb3 100644 --- a/www/common/common-history.js +++ b/www/common/common-history.js @@ -35,8 +35,8 @@ define([ }; var realtime = createRealtime(); - var hash = config.href ? common.parsePadUrl(config.href).hash : undefined; - var secret = common.getSecrets(hash); + var parsed = config.href ? common.parsePadUrl(config.href) : {}; + var secret = common.getSecrets(parsed.type, parsed.hash); var crypto = Crypto.createEncryptor(secret.keys); var to = window.setTimeout(function () { diff --git a/www/common/cryptget.js b/www/common/cryptget.js index 382ab023c..cde78d298 100644 --- a/www/common/cryptget.js +++ b/www/common/cryptget.js @@ -22,7 +22,8 @@ define([ }; var makeConfig = function (hash) { - var secret = Cryptpad.getSecrets(hash); + // We can't use cryptget with a file or a user so we can use 'pad' as hash type + var secret = Cryptpad.getSecrets('pad', hash); if (!secret.keys) { secret.keys = secret.key; } // support old hashses var config = { websocketURL: Cryptpad.getWebsocketURL(), diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 73b51e8b3..457102032 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -75,10 +75,10 @@ define([ // import hash utilities for export var createRandomHash = common.createRandomHash = Hash.createRandomHash; + var parseTypeHash = common.parseTypeHash = Hash.parseTypeHash; var parsePadUrl = common.parsePadUrl = Hash.parsePadUrl; var isNotStrongestStored = common.isNotStrongestStored = Hash.isNotStrongestStored; var hrefToHexChannelId = common.hrefToHexChannelId = Hash.hrefToHexChannelId; - var parseHash = common.parseHash = Hash.parseHash; var getRelativeHref = common.getRelativeHref = Hash.getRelativeHref; common.getBlobPathFromHex = Hash.getBlobPathFromHex; @@ -286,12 +286,12 @@ define([ if (!pad.title) { pad.title = common.getDefaultname(parsed); } - return parsed.hash; + return parsed.hashData; }; // Migrate from legacy store (localStorage) var migrateRecentPads = common.migrateRecentPads = function (pads) { return pads.map(function (pad) { - var hash; + var parsedHash; if (Array.isArray(pad)) { // TODO DEPRECATE_F var href = pad[0]; href.replace(/\#(.*)$/, function (a, h) { @@ -305,8 +305,8 @@ define([ ctime: pad[1], }; } else if (pad && typeof(pad) === 'object') { - hash = checkObjectData(pad); - if (!hash || !common.parseHash(hash)) { return; } + parsedHash = checkObjectData(pad); + if (!parsedHash || !parsedHash.type) { return; } return pad; } else { console.error("[Cryptpad.migrateRecentPads] pad had unexpected value"); @@ -319,8 +319,8 @@ define([ var checkRecentPads = common.checkRecentPads = function (pads) { pads.forEach(function (pad, i) { if (pad && typeof(pad) === 'object') { - var hash = checkObjectData(pad); - if (!hash || !common.parseHash(hash)) { + var parsedHash = checkObjectData(pad); + if (!parsedHash || !parsedHash.type) { console.error("[Cryptpad.checkRecentPads] pad had unexpected value", pad); getStore().removeData(i); return; @@ -538,6 +538,7 @@ define([ common.setPadTitle = function (name, cb) { var href = window.location.href; var parsed = parsePadUrl(href); + if (!parsed.hash) { return; } href = getRelativeHref(href); // getRecentPads return the array from the drive, not a copy // We don't have to call "set..." at the end, everything is stored with listmap @@ -558,8 +559,8 @@ define([ // Version 1 : we have up to 4 differents hash for 1 pad, keep the strongest : // Edit > Edit (present) > View > View (present) - var pHash = parseHash(p.hash); - var parsedHash = parseHash(parsed.hash); + var pHash = p.hashData; + var parsedHash = parsed.hashData; if (!pHash) { return; } // We may have a corrupted pad in our storage, abort here in that case @@ -661,7 +662,8 @@ define([ var userHash = localStorage && localStorage.User_hash; if (!userHash) { return null; } - var userChannel = common.parseHash(userHash).channel; + var userParsedHash = common.parseTypeHash('drive', userHash); + var userChannel = userParsedHash && userParsedHash.channel; if (!userChannel) { return null; } var list = fo.getFiles([fo.FILES_DATA]).map(hrefToHexChannelId) @@ -1273,20 +1275,21 @@ define([ UI.Alertify.reset(); // Load the new pad when the hash has changed - var oldHash = document.location.hash.slice(1); + var oldHref = document.location.href; window.onhashchange = function () { - var newHash = document.location.hash.slice(1); - var parsedOld = parseHash(oldHash); - var parsedNew = parseHash(newHash); + var newHref = document.location.href; + var parsedOld = parsePadUrl(oldHref).hashData; + var parsedNew = parsePadUrl(newHref).hashData; if (parsedOld && parsedNew && ( - parsedOld.channel !== parsedNew.channel + parsedOld.type !== parsedNew.type + || parsedOld.channel !== parsedNew.channel || parsedOld.mode !== parsedNew.mode || parsedOld.key !== parsedNew.key)) { document.location.reload(); return; } if (parsedNew) { - oldHash = newHash; + oldHref = newHref; } }; diff --git a/www/common/fsStore.js b/www/common/fsStore.js index 1be9e797f..b20c38c48 100644 --- a/www/common/fsStore.js +++ b/www/common/fsStore.js @@ -222,7 +222,7 @@ define([ if (!hash) { throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...'); } - var secret = Cryptpad.getSecrets(hash); + var secret = Cryptpad.getSecrets('drive', hash); var listmapConfig = { data: {}, websocketURL: Cryptpad.getWebsocketURL(), diff --git a/www/common/userObject.js b/www/common/userObject.js index 0972b6943..49e46147a 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -61,9 +61,7 @@ define([ if (!isFile(element)) { return false; } var parsed = Cryptpad.parsePadUrl(element); if (!parsed) { return false; } - var hash = parsed.hash; - var pHash = Cryptpad.parseHash(hash); - if (pHash && !pHash.mode) { return; } + var pHash = parsed.hashData; return pHash && pHash.mode === 'view'; }; diff --git a/www/drive/main.js b/www/drive/main.js index 47868bde4..10921e20c 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -1103,7 +1103,7 @@ define([ var type = Messages.type[hrefData.type] || hrefData.type; var $title = $('', {'class': 'title listElement', title: data.title}).text(data.title); var $type = $('', {'class': 'type listElement', title: type}).text(type); - if (hrefData.hash && Cryptpad.parseHash(hrefData.hash) && Cryptpad.parseHash(hrefData.hash).mode === 'view') { + if (hrefData.hashData && hrefData.hashData.mode === 'view') { $type.append(' (' + Messages.readonly+ ')'); } var $adate = $('', {'class': 'atime listElement', title: getDate(data.atime)}).text(getDate(data.atime)); @@ -2155,9 +2155,9 @@ define([ var getReadOnlyUrl = APP.getRO = function (href) { if (!filesOp.isFile(href)) { return; } var i = href.indexOf('#') + 1; - var hash = href.slice(i); + var parsed = Cryptpad.parsePadUrl(href);; var base = href.slice(0, i); - var hrefsecret = Cryptpad.getSecrets(hash); + var hrefsecret = Cryptpad.getSecrets(parsed.type, parsed.hash); if (!hrefsecret.keys) { return; } var viewHash = Cryptpad.getViewHashFromKeys(hrefsecret.channel, hrefsecret.keys); return base + viewHash; @@ -2608,7 +2608,7 @@ define([ } var hash = window.location.hash.slice(1) || Cryptpad.getUserHash() || localStorage.FS_hash; - var secret = Cryptpad.getSecrets(hash); + var secret = Cryptpad.getSecrets('drive', hash); var readOnly = APP.readOnly = secret.keys && !secret.keys.editKeyStr; var listmapConfig = module.config = {