diff --git a/.gitignore b/.gitignore index 741aedaf7..c95495f31 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ npm-debug.log pins/ blob/ blobstage/ +block/ privileged.conf diff --git a/CHANGELOG.md b/CHANGELOG.md index fd2c1059f..8a9a00a54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,45 @@ +# Echidna release (v2.4.0) + +## Goals + +For version 2.4.0 we chose to use our time to address difficulties that some users had, and to release some features which have been in development for some time. With the recent release of the _password-protected-pads_ feature, some users desired to be able to change the passwords that they'd already set, or to add a password to a pad retroactively. Other users wanted to recover information that had accidentally been deleted from their pads, but found that the history feature was difficult to use on networks with poor connectivity. Others still found that loading pads in general was too slow. + +## Update notes + +* We have released new clientside dependencies, so server administrators will need to run `bower update` +* This release also depends on new serverside dependencies, so administrators will also need to run `npm update` +* This release (optionally) takes advantage of Webworker APIs, so administrators may need to update their Content Security Headers to include worker-src (and child-src for safari) + * see cryptpad/docs/example.nginx.conf for more details regarding configuration for nginx as a reverse proxy + * to enable webworkers as an experimental feature, add `AppConfig.disableWorkers = false;` to your `cryptpad/customize/application-config.js` +* Finally, administrators will need to restart their servers after updating, as clients will require new functionality + +## What's new + +### Features + +* CryptPad now takes advantage of some very modern browser APIs + * Shared Workers allow common tasks for all CryptPad editors to be handled by a single background process which runs in the background. This results in better performance savings for anyone using multiple editors at once in different tabs + * Webworkers are used in situations where shared workers are not supported, for most of the same tasks. They are not shared amongst different tabs, but can allow for a more responsive user experience since some heavy commands will be run in the background + * Not all browsers feature complete support for webworkers. For cases where they are not supported at all, or where cryptographic APIs are not supported within their context (https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7607496/), we fall back to an asynchronous context in the same thread +* Pads with no password can now be updated to include a password, and pads with a password can have their passwords changed + * right-click on the pad in question, and see its properties. The following dialog will present the option to change its password + * changing a pad's password will remove its history +* Accessing a pad's history used to require that clients fetch the entire history of the pad before they could view any of it. History retrieval is now done on an on-demand basis, approximately 100 versions of the pad at a time + * this also features an updated UI with a slider +* We've refactored our whiteboard application to be compatible with our internal framework. As a result, it will be easier to maintain and will have all the same features as the other editors built with the same framework +* We've defined some new server-side features which will allow clients to change their user passwords in a coming release +* We've updated our messaging server implementation + * the aspect of the server which stores and distributes history has been untangled from the aspect which tracks user lists and broadcasts messages + * the server will now store the time when each message was received, so as to be able to allow users to view the time of edits in a later release + +### Bug fixes + +* When a user tries to register, but enters credentials which have already been used for that CryptPad instance, we prompt them to log in as that user. We discovered that the login had stopped working at some point. This has been fixed +* Server administrators may have seen warnings from npm when attempting to update. We have fixed invalid entries and added missing entries where appropriate such that there are no more warnings +* Static info pages have been restyled to be more responsive, thanks to @CatalinScr +* Support for friend requests in pads with version 0 hashes has been repaired +* We noticed a regression in how default titles for pads were suggested, and have implemented the intended behaviour + # Donkey release (v2.3.0) ## Goals diff --git a/customize.dist/translations/messages.de.js b/customize.dist/translations/messages.de.js index 4e68d9f29..6826230da 100644 --- a/customize.dist/translations/messages.de.js +++ b/customize.dist/translations/messages.de.js @@ -42,6 +42,7 @@ out.chainpadError = 'Ein kritischer Fehler hat stattgefunden, bei den Updates deines Dokuments. Dieses Dokument ist schreibgeschützt, damit du sicher machen kannst, dass keine Inhalt verloren geht.
'+ 'Druck auf Esc, um das Dokument schreibgeschützt zu lesen, oder lade es neu, um das Editierien wiederanzufangen.'; out.errorCopy = ' Du kannst noch den Inhalt woanders kopieren, nachdem du Esc drucken.
Wenn du die Seite verlässt, verschwindet der Inhalt für immer!'; + out.errorRedirectToHome = 'Drucke Esc, um zu deinem CryptDrive zu gehen.'; out.loading = "Laden..."; out.error = "Fehler"; diff --git a/docs/example.nginx.conf b/docs/example.nginx.conf index 44b12ade8..afbd86846 100644 --- a/docs/example.nginx.conf +++ b/docs/example.nginx.conf @@ -6,12 +6,11 @@ server { listen 443 ssl http2; + server_name your-main-domain.com your-sandbox-domain.com; - server_name cryptpad.fr www.cryptpad.fr beta.cryptpad.fr; - - ssl_certificate /home/cryptpad/.acme.sh/alpha.cryptpad.fr/fullchain.cer; - ssl_certificate_key /home/cryptpad/.acme.sh/alpha.cryptpad.fr/alpha.cryptpad.fr.key; - ssl_trusted_certificate /home/cryptpad/.acme.sh/alpha.cryptpad.fr/ca.cer; + ssl_certificate /home/cryptpad/.acme.sh/your-main-domain.com/fullchain.cer; + ssl_certificate_key /home/cryptpad/.acme.sh/your-main-domain.com/your-main-domain.com.key; + ssl_trusted_certificate /home/cryptpad/.acme.sh/your-main-domain.com/ca.cer; ssl_dhparam /etc/nginx/dhparam.pem; ssl_session_timeout 5m; @@ -27,6 +26,7 @@ server { root /home/cryptpad/cryptpad; index index.html; + error_page 404 /customize.dist/404.html; if ($args ~ ver=) { set $cacheControl max-age=31536000; @@ -34,25 +34,31 @@ server { # Will not set any header if it is emptystring add_header Cache-Control $cacheControl; - set $styleSrc "'unsafe-inline' 'self'"; - set $scriptSrc "'self'"; - set $connectSrc "'self' wss://cryptpad.fr wss://api.cryptpad.fr"; - set $fontSrc "'self'"; + set $styleSrc "'unsafe-inline' 'self' your-main-domain.com"; + set $scriptSrc "'self' your-main-domain.com"; + set $connectSrc "'self' https://your-main-domain.com wss://your-main-domain.com https://api.your-main-domain.com wss://your-main-domain.com your-main-domain.com blob: your-main-domain.com"; + set $fontSrc "'self' data: your-main-domain.com"; set $imgSrc "data: * blob:"; - set $frameSrc "'self' beta.cryptpad.fr"; - - if ($uri = /pad/inner.html) { - set $scriptSrc "'self' 'unsafe-eval' 'unsafe-inline'"; + set $frameSrc "'self' your-sandbox-domain.com blob:"; + set $mediaSrc "* blob:"; + set $childSrc "https://your-main-domain.com"; + set $workerSrc "https://your-main-domain.com"; + + set $unsafe 0; + if ($uri = "/pad/inner.html") { set $unsafe 1; } + if ($host != sandbox.cryptpad.info) { set $unsafe 0; } + if ($unsafe) { + set $scriptSrc "'self' 'unsafe-eval' 'unsafe-inline' new2.cryptpad.fr cryptpad.fr"; } - add_header Content-Security-Policy "default-src 'none'; style-src $styleSrc; script-src $scriptSrc; connect-src $connectSrc; font-src $fontSrc; img-src $imgSrc; frame-src $frameSrc;"; + add_header Content-Security-Policy "default-src 'none'; child-src $childSrc; worker-src $workerSrc; media-src $mediaSrc; style-src $styleSrc; script-src $scriptSrc; connect-src $connectSrc; font-src $fontSrc; img-src $imgSrc; frame-src $frameSrc;"; + - location = /cryptpad_websocket { + location ^~ /cryptpad_websocket { proxy_pass http://localhost:3000; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; -# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # WebSocket support (nginx 1.4) proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; @@ -72,8 +78,8 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - + } + location ^~ /blob/ { add_header Cache-Control max-age=31536000; try_files $uri =404; @@ -84,11 +90,9 @@ server { try_files $uri =404; } - ## TODO fix in the code so that we don't need this - location ~ ^/(register|login|settings|user|pad|drive|poll|slide|code|whiteboard|file|media)$ { + location ~ ^/(register|login|settings|user|pad|drive|poll|slide|code|whiteboard|file|media|profile|contacts|todo|filepicker|debug|kanban)$ { rewrite ^(.*)$ $1/ redirect; } try_files /www/$uri /www/$uri/index.html /customize/$uri; } - diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 4b0c2c607..d5066b757 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -398,10 +398,16 @@ Version 1 Hash.findWeaker = function (href, channel, recents) { var parsed = parsePadUrl(href); if (!parsed.hash) { return false; } + // We can't have a weaker hash if we're already in view mode + if (parsed.hashData && parsed.hashData.mode === 'view') { return; } var weaker; Object.keys(recents).some(function (id) { var pad = recents[id]; - var p = parsePadUrl(pad.href); + if (pad.href || !pad.roHref) { + // This pad has an edit link, so it can't be weaker + return; + } + var p = parsePadUrl(pad.roHref); if (p.type !== parsed.type) { return; } // Not the same type if (p.hash === parsed.hash) { return; } // Same hash, not stronger if (channel !== pad.channel) { return; } // Not the same channel @@ -430,6 +436,10 @@ Version 1 var stronger; Object.keys(recents).some(function (id) { var pad = recents[id]; + if (!pad.href) { + // This pad doesn't have an edit link, so it can't be stronger + return; + } 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 diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 7b05d22fe..5e5c75176 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -665,7 +665,7 @@ define([ // Update the current state loading.driveState = data.state; data.progress = data.progress || 100; - data.msg = Messages['loading_drive_'+data.state] || ''; + data.msg = Messages['loading_drive_'+ Math.floor(data.state)] || ''; $progress.html(data.msg); if (data.progress) { $progress.append(h('div.cp-loading-progress-bar', [ @@ -761,7 +761,7 @@ define([ UI.getFileIcon = function (data) { var $icon = UI.getIcon(); if (!data) { return $icon; } - var href = data.href; + var href = data.href || data.roHref; var type = data.type; if (!href && !type) { return $icon; } diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index d54540efa..3d057ed04 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -73,26 +73,14 @@ define([ data.password = val; })); }).nThen(function (waitFor) { + var base = common.getMetadataMgr().getPrivateData().origin; common.getPadAttribute('href', waitFor(function (err, val) { - var base = common.getMetadataMgr().getPrivateData().origin; - - var parsed = Hash.parsePadUrl(val); - if (parsed.hashData.mode === "view") { - data.roHref = base + val; - return; - } - - // We're not in a read-only pad + if (!val) { return; } data.href = base + val; - - // Get Read-only href - if (parsed.hashData.type !== "pad") { return; } - var i = data.href.indexOf('#') + 1; - var hBase = data.href.slice(0, i); - var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash, data.password); - if (!hrefsecret.keys) { return; } - var viewHash = Hash.getViewHashFromKeys(hrefsecret); - data.roHref = hBase + viewHash; + })); + common.getPadAttribute('roHref', waitFor(function (err, val) { + if (!val) { return; } + data.roHref = base + val; })); common.getPadAttribute('channel', waitFor(function (err, val) { data.channel = val; @@ -162,7 +150,7 @@ define([ $d.append(password); } - var parsed = Hash.parsePadUrl(data.href); + var parsed = Hash.parsePadUrl(data.href || data.roHref); if (owned && parsed.hashData.type === 'pad') { var sframeChan = common.getSframeChannel(); var changePwTitle = Messages.properties_changePassword; @@ -186,7 +174,7 @@ define([ UI.confirm(changePwConfirm, function (yes) { if (!yes) { return; } sframeChan.query("Q_PAD_PASSWORD_CHANGE", { - href: data.href, + href: data.href || data.roHref, password: $(newPassword).find('input').val() }, function (err, data) { if (err || data.error) { @@ -195,11 +183,11 @@ define([ UI.findOKButton().click(); if (data.warning) { return void UI.alert(Messages.properties_passwordWarning, function () { - common.gotoURL(hasPassword ? undefined : data.href); + common.gotoURL(hasPassword ? undefined : (data.href || data.roHref)); }, {force: true}); } return void UI.alert(Messages.properties_passwordSuccess, function () { - common.gotoURL(hasPassword ? undefined : data.href); + common.gotoURL(hasPassword ? undefined : (data.href || data.roHref)); }, {force: true}); }); }); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 589ec0292..afd177577 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -613,7 +613,7 @@ define([ if (!parsed.hash) { return void cb({ error: 'EINVAL_HREF' }); } var warning = false; - var newHash; + var newHash, newRoHref; var oldChannel; if (parsed.hashData.password) { newHash = parsed.hash; @@ -678,6 +678,11 @@ define([ common.setPadAttribute('channel', secret.channel, waitFor(function (err) { if (err) { warning = true; } }), href); + var viewHash = Hash.getViewHashFromKeys(secret); + newRoHref = '/' + parsed.type + '/#' + viewHash; + common.setPadAttribute('roHref', newRoHref, waitFor(function (err) { + if (err) { warning = true; } + }), href); if (parsed.hashData.password) { return; } // same hash common.setPadAttribute('href', newHref, waitFor(function (err) { @@ -687,7 +692,8 @@ define([ cb({ warning: warning, hash: newHash, - href: newHref + href: newHref, + roHref: newRoHref }); }); }; diff --git a/www/common/mergeDrive.js b/www/common/mergeDrive.js index ea4c6edc7..020861481 100644 --- a/www/common/mergeDrive.js +++ b/www/common/mergeDrive.js @@ -115,23 +115,25 @@ define([ var newRecentPads = proxy.drive[newFo.FILES_DATA]; var oldFiles = oldFo.getFiles([newFo.FILES_DATA]); var newHrefs = Object.keys(newRecentPads).map(function (id) { - return newRecentPads[id].href; + return newRecentPads[id].href || newRecentPads[id].roHref; }); oldFiles.forEach(function (id) { - var href = oldRecentPads[id].href; + var href = oldRecentPads[id].href || oldRecentPads[id].roHref; + var isRo = href === oldRecentPads[id].roHref; // Do not migrate a pad if we already have it, it would create a duplicate in the drive if (newHrefs.indexOf(href) !== -1) { return; } // If we have a stronger version, do not add the current href - if (Hash.findStronger(href, oldRecentPads[id].channel, newRecentPads)) { return; } + // If the current href is read-only, don't check, we won't have a stronger + if (isRo && Hash.findStronger(href, oldRecentPads[id].channel, newRecentPads)) { return; } // If we have a weaker version, replace the href by the new one - // NOTE: if that weaker version is in the trash, the strong one will be put in unsorted - var weaker = Hash.findWeaker(href, oldRecentPads[id].channel, newRecentPads); - if (weaker) { - // Update RECENTPADS - weaker.href = href; - // Update the file in the drive - newFo.replace(weaker.href, href); - return; + // If the current href is an edit link, don't check, we won't have a weaker + if (!isRo) { + var weaker = Hash.findWeaker(href, oldRecentPads[id].channel, newRecentPads); + if (weaker) { + // Update RECENTPADS + weaker.href = href; + return; + } } // Here it means we have a new href, so we should add it to the drive at its old location var paths = oldFo.findFile(id); diff --git a/www/common/migrate-user-object.js b/www/common/migrate-user-object.js index fb69fb20b..2f1ea5e88 100644 --- a/www/common/migrate-user-object.js +++ b/www/common/migrate-user-object.js @@ -123,12 +123,58 @@ define([ })); }); }); - n.nThen(waitFor()); + n.nThen(waitFor(function () { + Feedback.send('Migrate-6', true); + userObject.version = version = 6; + })); }; if (version < 6) { addChannelId(); - Feedback.send('Migrate-6', true); - userObject.version = version = 6; + } + }).nThen(function (waitFor) { + var addRoHref = function () { + var data = userObject.drive.filesData; + var el, parsed; + var n = nThen(function () {}); + var padsLength = Object.keys(data).length; + Object.keys(data).forEach(function (k, i) { + n = n.nThen(function (w) { + setTimeout(w(function () { + el = data[k]; + if (!el.href || (el.roHref && false)) { + // Already migrated + return void progress(7, Math.round(100*i/padsLength)); + } + parsed = Hash.parsePadUrl(el.href); + if (parsed.hashData.type !== "pad") { + // No read-only mode for files + return void progress(7, Math.round(100*i/padsLength)); + } + if (parsed.hashData.mode === "view") { + // This is a read-only pad in our drive + el.roHref = el.href; + delete el.href; + console.log('Move href to roHref in filesData ', el.roHref); + } else { + var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); + var hash = Hash.getViewHashFromKeys(secret); + if (hash) { + // Version 0 won't have a view hash available + el.roHref = '/' + parsed.type + '/#' + hash; + console.log('Adding missing roHref in filesData ', el.href); + } + } + progress(6, Math.round(100*i/padsLength)); + })); + }); + }); + n.nThen(waitFor(function () { + Feedback.send('Migrate-7', true); + userObject.version = version = 7; + })); + }; + if (version < 7) { + addRoHref(); } /*}).nThen(function (waitFor) { // Test progress bar in the loading screen diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 01d2bd62b..35d3dba67 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -426,10 +426,11 @@ define([ cb(JSON.parse(JSON.stringify(metadata))); }; - var makePad = function (href, title) { + var makePad = function (href, roHref, title) { var now = +new Date(); return { href: href, + roHref: roHref, atime: now, ctime: now, title: title || Hash.getDefaultName(Hash.parsePadUrl(href)), @@ -437,8 +438,15 @@ define([ }; Store.addPad = function (clientId, data, cb) { - if (!data.href) { return void cb({error:'NO_HREF'}); } - var pad = makePad(data.href, data.title); + if (!data.href && !data.roHref) { return void cb({error:'NO_HREF'}); } + if (!data.roHref) { + var parsed = Hash.parsePadUrl(data.href); + if (parsed.hashData.type === "pad") { + var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password); + data.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret); + } + } + var pad = makePad(data.href, data.roHref, data.title); if (data.owners) { pad.owners = data.owners; } if (data.expire) { pad.expire = data.expire; } if (data.password) { pad.password = data.password; } @@ -736,9 +744,9 @@ define([ // Edit > Edit (present) > View > View (present) for (var id in allPads) { var pad = allPads[id]; - if (!pad.href) { continue; } + if (!pad.href && !pad.roHref) { continue; } - var p2 = Hash.parsePadUrl(pad.href); + var p2 = Hash.parsePadUrl(pad.href || pad.roHref); var h2 = p2.hashData; // Different types, proceed to the next one @@ -789,8 +797,14 @@ define([ // Add the pad if it does not exist in our drive if (!contains) { + var roHref; + if (h.mode === "view") { + roHref = href; + href = undefined; + } Store.addPad(clientId, { href: href, + roHref: roHref, channel: channel, title: title, owners: owners, @@ -827,7 +841,7 @@ define([ }; store.userObject.getFiles(where).forEach(function (id) { var data = store.userObject.getFileData(id); - var parsed = Hash.parsePadUrl(data.href); + var parsed = Hash.parsePadUrl(data.href || data.roHref); if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) && hashes.indexOf(parsed.hash) === -1 && !isFiltered(parsed.type, data)) { @@ -1421,7 +1435,7 @@ define([ }).nThen(function (waitFor) { Migrate(proxy, waitFor(), function (version, progress) { postMessage(clientId, 'LOADING_DRIVE', { - state: 2, + state: (2 + (version / 10)), progress: progress }); }); diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 31e70a3bb..4bc0019c3 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -50,19 +50,6 @@ define([ var data = exp.getFileData(id); cb(null, clone(data[attr])); }; - var removePadAttribute = exp.removePadAttribute = function (f) { - if (typeof(f) !== 'string') { - console.error("Can't find pad attribute for an undefined pad"); - return; - } - Object.keys(files).forEach(function (key) { - var hash = f.indexOf('#') !== -1 ? f.slice(f.indexOf('#') + 1) : null; - if (hash && key.indexOf(hash) === 0) { - exp.debug("Deleting pad attribute in the realtime object"); - delete files[key]; - } - }); - }; exp.pushData = function (data, cb) { if (typeof cb !== "function") { cb = function () {}; } @@ -145,12 +132,9 @@ define([ if (!loggedIn && !config.testMode) { allFilesPaths.forEach(function (path) { - var el = exp.find(path); - if (!el) { return; } - var id = exp.getIdFromHref(el.href); + var id = path[1]; if (!id) { return; } spliceFileData(id); - removePadAttribute(el.href); }); return; } @@ -259,7 +243,6 @@ define([ if (!id) { return; } if (!loggedIn && !config.testMode) { // delete permanently - exp.removePadAttribute(href); spliceFileData(id); return; } @@ -268,14 +251,7 @@ define([ }; // REPLACE - exp.replace = function (o, n) { - var idO = exp.getIdFromHref(o); - if (!idO || !exp.isFile(idO)) { return; } - var data = exp.getFileData(idO); - if (!data) { return; } - data.href = n; - }; - // If all the occurences of an href are in the trash, remvoe them and add the file in root. + // If all the occurences of an href are in the trash, remove them and add the file in root. // This is use with setPadTitle when we open a stronger version of a deleted pad exp.restoreHref = function (href) { var idO = exp.getIdFromHref(href); @@ -563,13 +539,15 @@ define([ continue; } // Clean missing href - if (!el.href) { + if (!el.href && !el.roHref) { debug("Removing an element in filesData with a missing href.", el); toClean.push(id); continue; } - var parsed = Hash.parsePadUrl(el.href); + var parsed = Hash.parsePadUrl(el.href || el.roHref); + var secret; + // Clean invalid hash if (!parsed.hash) { debug("Removing an element in filesData with a invalid href.", el); @@ -583,6 +561,23 @@ define([ continue; } + // If we have an edit link, check the view link + if (el.href && parsed.hashData.type === "pad") { + if (parsed.hashData.mode === "view") { + el.roHref = el.href; + delete el.href; + } else if (!el.roHref) { + secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); + el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret); + } else { + var parsed2 = Hash.parsePadUrl(el.roHref); + if (!parsed2.hash || !parsed2.type) { + secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); + el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret); + } + } + } + // Fix href if (/^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); } // Fix creation time @@ -592,7 +587,9 @@ define([ // Fix channel if (!el.channel) { try { - var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); + if (!secret) { + secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); + } el.channel = secret.channel; console.log('Adding missing channel in filesData ', el.channel); } catch (e) { diff --git a/www/common/userObject.js b/www/common/userObject.js index 0042d5d57..f44310296 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -79,11 +79,7 @@ define([ exp.isReadOnlyFile = function (element) { if (!isFile(element)) { return false; } var data = exp.getFileData(element); - var parsed = Hash.parsePadUrl(data.href); - if (!parsed) { return false; } - var pHash = parsed.hashData; - if (!pHash || pHash.type !== "pad") { return; } - return pHash && pHash.mode === 'view'; + return Boolean(data.roHref && !data.href); }; var isFolder = exp.isFolder = function (element) { @@ -140,7 +136,7 @@ define([ var getTitle = exp.getTitle = function (file, type) { if (workgroup) { debug("No titles in workgroups"); return; } var data = getFileData(file); - if (!file || !data || !data.href) { + if (!file || !data || !(data.href || data.roHref)) { error("getTitle called with a non-existing file id: ", file, data); return; } @@ -289,7 +285,8 @@ define([ var getIdFromHref = exp.getIdFromHref = function (href) { var result; getFiles([FILES_DATA]).some(function (id) { - if (files[FILES_DATA][id].href === href) { + if (files[FILES_DATA][id].href === href || + files[FILES_DATA][id].roHref === href) { result = id; return true; } diff --git a/www/drive/inner.js b/www/drive/inner.js index d072992d6..fb6f56bf4 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -666,10 +666,10 @@ define([ var openFile = function (el, href) { if (!href) { var data = filesOp.getFileData(el); - if (!data || !data.href) { + if (!data || (!data.href && !data.roHref)) { return void logError("Missing data for the file", el, data); } - href = data.href; + href = data.href || data.roHref; } window.open(APP.origin + href); }; @@ -1271,9 +1271,10 @@ define([ if (!filesOp.isFile(element)) { return; } var data = filesOp.getFileData(element); + var href = data.href || data.roHref; if (!data) { return void logError("No data for the file", element); } - var hrefData = Hash.parsePadUrl(data.href); + var hrefData = Hash.parsePadUrl(href); if (hrefData.type) { $span.addClass('cp-border-color-'+hrefData.type); } @@ -1305,7 +1306,7 @@ define([ $span.attr('title', name); var type = Messages.type[hrefData.type] || hrefData.type; - common.displayThumbnail(data.href, data.channel, data.password, $span, function ($thumb) { + common.displayThumbnail(href || data.roHref, data.channel, data.password, $span, function ($thumb) { // Called only if the thumbnail exists // Remove the .hide() added by displayThumnail() because it hides the icon in // list mode too @@ -1847,7 +1848,7 @@ define([ var data = filesOp.getFileData(id); if (!data) { return ''; } if (prop === 'type') { - var hrefData = Hash.parsePadUrl(data.href); + var hrefData = Hash.parsePadUrl(data.href || data.roHref); return hrefData.type; } if (prop === 'atime' || prop === 'ctime') { @@ -1882,7 +1883,7 @@ define([ }; } if (prop === 'type') { - var hrefData = Hash.parsePadUrl(e.href); + var hrefData = Hash.parsePadUrl(e.href || e.roHref); return hrefData.type; } if (prop === 'atime' || prop === 'ctime') { @@ -2690,20 +2691,6 @@ define([ return $div.html(); }; - var getReadOnlyUrl = APP.getRO = function (id) { - if (!filesOp.isFile(id)) { return; } - var data = filesOp.getFileData(id); - if (!data) { return; } - var parsed = Hash.parsePadUrl(data.href); - if (parsed.hashData.type !== "pad") { return; } - var i = data.href.indexOf('#') + 1; - var base = data.href.slice(0, i); - var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash, data.password); - if (!hrefsecret.keys) { return; } - var viewHash = Hash.getViewHashFromKeys(hrefsecret); - return base + viewHash; - }; - // Disable middle click in the context menu to avoid opening /drive/inner.html# in new tabs $(window).click(function (e) { if (!e.target || !$(e.target).parents('.cp-dropdown-content').length) { return; } @@ -2717,19 +2704,16 @@ define([ if (!filesOp.isFile(el)) { return void cb('NOT_FILE'); } - var ro = filesOp.isReadOnlyFile(el); + //var ro = filesOp.isReadOnlyFile(el); var base = APP.origin; var data = JSON.parse(JSON.stringify(filesOp.getFileData(el))); - if (!data || !data.href) { return void cb('INVALID_FILE'); } - data.href = base + data.href; + if (!data || !(data.href || data.roHref)) { return void cb('INVALID_FILE'); } - var roUrl; - if (ro) { - data.roHref = data.href; - delete data.href; - } else { - roUrl = getReadOnlyUrl(el); - if (roUrl) { data.roHref = base + roUrl; } + if (data.href) { + data.href = base + data.href; + } + if (data.roHref) { + data.roHref = base + data.roHref; } UIElements.getProperties(common, data, cb); @@ -2804,10 +2788,15 @@ define([ else if ($(this).hasClass('cp-app-drive-context-openro')) { paths.forEach(function (p) { var el = filesOp.find(p.path); - if (filesOp.isPathIn(p.path, [FILES_DATA])) { el = el.href; } - if (!el || filesOp.isFolder(el)) { return; } - var roUrl = getReadOnlyUrl(el); - openFile(null, roUrl); + var href; + if (filesOp.isPathIn(p.path, [FILES_DATA])) { + href = el.roHref; + } else { + if (!el || filesOp.isFolder(el)) { return; } + var data = filesOp.getFileData(el); + href = data.roHref; + } + openFile(null, href); }); } else if ($(this).hasClass('cp-app-drive-context-newfolder')) { @@ -2847,7 +2836,7 @@ define([ el = filesOp.find(paths[0].path); var data = filesOp.getFileData(el); if (!data) { return void console.error("Expected to find a file"); } - var href = data.href; + var href = data.href || data.roHref; common.updateTags(href); } else if ($(this).hasClass("cp-app-drive-context-empty")) { diff --git a/www/drive/tests.js b/www/drive/tests.js index a5fec1145..67d986d86 100644 --- a/www/drive/tests.js +++ b/www/drive/tests.js @@ -237,7 +237,8 @@ define([ && typeof files.template[0] === "number" && typeof files.filesData[files.template[0]] === "object" && !files.filesData[files.template[0]].filename - && files.filesData[files.template[0]].href === href3 + && !files.filesData[files.template[0]].href + && files.filesData[files.template[0]].roHref === href3 && typeof fileId2 === "number" && typeof files.filesData[fileId2] === "object" && files.filesData[fileId2].filename === "Trash" @@ -392,11 +393,6 @@ define([ console.log("DRIVE operations: rename"); return cb(); } - fo.replace(href1, href2); - if (fo.getFileData(id1).href !== href2) { - console.log("DRIVE operations: replace"); - return cb(); - } cb(true); }, "DRIVE operations");