From 96a71afbc964a72022c8d5aad7377fd2f57d4b05 Mon Sep 17 00:00:00 2001 From: Paul Libbrecht Date: Fri, 11 May 2018 09:53:20 +0200 Subject: [PATCH 1/8] Translating one extra line. --- customize.dist/translations/messages.de.js | 1 + 1 file changed, 1 insertion(+) diff --git a/customize.dist/translations/messages.de.js b/customize.dist/translations/messages.de.js index 06b86968c..a02e2ab83 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"; From 04d080d5a87aea73af7ac88c933ec8ad2690f4df Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 27 Jun 2018 13:48:36 +0200 Subject: [PATCH 2/8] 2.4.0 changelog --- CHANGELOG.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd2c1059f..1c1c5c256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,42 @@ +# 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 administraotrs will also need to run `npm update` +* 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 From 2c56972be62f6eb41a8d2091d851171f991d97b3 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 27 Jun 2018 15:03:38 +0200 Subject: [PATCH 3/8] update changelog and example nginx config --- CHANGELOG.md | 2 ++ docs/example.nginx.conf | 46 ++++++++++++++++++++++------------------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c1c5c256..f8e061afd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ For version 2.4.0 we chose to use our time to address difficulties that some use * We have released new clientside dependencies, so server administrators will need to run `bower update` * This release also depends on new serverside dependencies, so administraotrs will also need to run `npm update` +* Since this release takes advantage of Webworker APIs, administrators will 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 * Finally, administrators will need to restart their servers after updating, as clients will require new functionality ## What's new 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; } - From fc6322b105d5c3c0015f7148328a935a695ed1bc Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 27 Jun 2018 15:03:48 +0200 Subject: [PATCH 4/8] ignore /block/ --- .gitignore | 1 + 1 file changed, 1 insertion(+) 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 From 76c9b01d45dd83bba8308f447b4496b1426047d7 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 27 Jun 2018 15:13:47 +0200 Subject: [PATCH 5/8] update changelog one more time --- CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8e061afd..8a9a00a54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,10 @@ For version 2.4.0 we chose to use our time to address difficulties that some use ## 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 administraotrs will also need to run `npm update` -* Since this release takes advantage of Webworker APIs, administrators will 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 +* 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 @@ -19,8 +20,8 @@ For version 2.4.0 we chose to use our time to address difficulties that some use * 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. + * 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 From 470f404a24380058c51721be819840d899a6659c Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 28 Jun 2018 15:31:30 +0200 Subject: [PATCH 6/8] temp --- www/common/common-interface.js | 2 +- www/common/common-ui-elements.js | 16 ++++++++++++---- www/common/cryptpad-common.js | 10 ++++++++-- www/drive/inner.js | 29 ++++++++++++++++++----------- 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 7b05d22fe..974490796 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -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..b35bebdfd 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -73,6 +73,8 @@ define([ data.password = val; })); }).nThen(function (waitFor) { + var base = common.getMetadataMgr().getPrivateData().origin; + /* XXX common.getPadAttribute('href', waitFor(function (err, val) { var base = common.getMetadataMgr().getPrivateData().origin; @@ -93,6 +95,12 @@ define([ if (!hrefsecret.keys) { return; } var viewHash = Hash.getViewHashFromKeys(hrefsecret); data.roHref = hBase + viewHash; + }));*/ + common.getPadAttribute('href', waitFor(function (err, val) { + data.href = base + val; + })); + common.getPadAttribute('roHref', waitFor(function (err, val) { + data.roHref = base + val; })); common.getPadAttribute('channel', waitFor(function (err, val) { data.channel = val; @@ -162,7 +170,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 +194,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 +203,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/drive/inner.js b/www/drive/inner.js index d072992d6..9e538b1f4 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,10 +2691,12 @@ define([ return $div.html(); }; + /* XXX var getReadOnlyUrl = APP.getRO = function (id) { if (!filesOp.isFile(id)) { return; } var data = filesOp.getFileData(id); if (!data) { return; } + if (data.roHref) { return data.roHref; } var parsed = Hash.parsePadUrl(data.href); if (parsed.hashData.type !== "pad") { return; } var i = data.href.indexOf('#') + 1; @@ -2702,7 +2705,7 @@ define([ 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) { @@ -2717,12 +2720,14 @@ 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; + data.roHref = base + data.roHref; + /* XXX var roUrl; if (ro) { data.roHref = data.href; @@ -2731,6 +2736,7 @@ define([ roUrl = getReadOnlyUrl(el); if (roUrl) { data.roHref = base + roUrl; } } + */ UIElements.getProperties(common, data, cb); }; @@ -2806,8 +2812,9 @@ define([ 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 roUrl = getReadOnlyUrl(el); + openFile(el); + //, roUrl); XXX }); } else if ($(this).hasClass('cp-app-drive-context-newfolder')) { @@ -2847,7 +2854,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")) { From 0f9a71686e338dc0d70dda0bd9f5b27dbe102abd Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 28 Jun 2018 18:16:34 +0200 Subject: [PATCH 7/8] Add support for read-only href stored in filesData --- www/common/common-hash.js | 12 ++++++++- www/common/mergeDrive.js | 23 ++++++++-------- www/common/outer/async-store.js | 24 ++++++++++++----- www/common/outer/userObject.js | 48 ++++++++++++++++++++------------- www/common/userObject.js | 8 ++++-- 5 files changed, 76 insertions(+), 39 deletions(-) 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/mergeDrive.js b/www/common/mergeDrive.js index ea4c6edc7..bcc30666e 100644 --- a/www/common/mergeDrive.js +++ b/www/common/mergeDrive.js @@ -115,23 +115,24 @@ 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; // 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/outer/async-store.js b/www/common/outer/async-store.js index 52c0efb9c..3837b338a 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,13 @@ 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); + 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 +742,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 +795,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 +839,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)) { diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 31e70a3bb..630f8aae1 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,14 @@ define([ if (!loggedIn && !config.testMode) { allFilesPaths.forEach(function (path) { + var id = path[1]; + /* XXX var el = exp.find(path); if (!el) { return; } var id = exp.getIdFromHref(el.href); + */ if (!id) { return; } spliceFileData(id); - removePadAttribute(el.href); }); return; } @@ -259,7 +248,6 @@ define([ if (!id) { return; } if (!loggedIn && !config.testMode) { // delete permanently - exp.removePadAttribute(href); spliceFileData(id); return; } @@ -268,6 +256,7 @@ define([ }; // REPLACE + /* XXX exp.replace = function (o, n) { var idO = exp.getIdFromHref(o); if (!idO || !exp.isFile(idO)) { return; } @@ -275,7 +264,8 @@ define([ 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 +553,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 +575,22 @@ define([ continue; } + // If we have an edit link, check the view link + if (el.href) { + var fixRo = function () { + secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); + el.roHref = '/' + parsed.type + '/#' + Hash.getViewHasFromKeys(secret); + }; + if (!el.roHref) { + fixRo(); + } else { + var parsed2 = Hash.parsePadUrl(el.roHref); + if (!parsed2.hash || !parsed2.type) { + fixRo(); + } + } + } + // Fix href if (/^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); } // Fix creation time @@ -592,7 +600,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 fb39eb484..4d8a6a0a2 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -78,11 +78,14 @@ define([ exp.isReadOnlyFile = function (element) { if (!isFile(element)) { return false; } var data = exp.getFileData(element); + return Boolean(data.roHref && !data.href); + /* XXX 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'; + */ }; var isFolder = exp.isFolder = function (element) { @@ -139,7 +142,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; } @@ -288,7 +291,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; } From 425ac8ea57bb30a9c502b69494c50ef105f97f22 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 29 Jun 2018 18:16:04 +0200 Subject: [PATCH 8/8] Migration for read-only links + fix issues with read-only pads --- www/common/common-interface.js | 2 +- www/common/common-ui-elements.js | 24 ++------------ www/common/mergeDrive.js | 1 + www/common/migrate-user-object.js | 52 +++++++++++++++++++++++++++++-- www/common/outer/async-store.js | 10 +++--- www/common/outer/userObject.js | 29 +++++------------ www/common/userObject.js | 7 ----- www/drive/inner.js | 50 ++++++++++------------------- www/drive/tests.js | 8 ++--- 9 files changed, 85 insertions(+), 98 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 974490796..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', [ diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index b35bebdfd..3d057ed04 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -74,32 +74,12 @@ define([ })); }).nThen(function (waitFor) { var base = common.getMetadataMgr().getPrivateData().origin; - /* XXX - 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 - 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('href', waitFor(function (err, val) { + if (!val) { return; } data.href = base + val; })); common.getPadAttribute('roHref', waitFor(function (err, val) { + if (!val) { return; } data.roHref = base + val; })); common.getPadAttribute('channel', waitFor(function (err, val) { diff --git a/www/common/mergeDrive.js b/www/common/mergeDrive.js index bcc30666e..020861481 100644 --- a/www/common/mergeDrive.js +++ b/www/common/mergeDrive.js @@ -119,6 +119,7 @@ define([ }); oldFiles.forEach(function (id) { 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 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 3837b338a..7981cec53 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -441,8 +441,10 @@ define([ if (!data.href && !data.roHref) { return void cb({error:'NO_HREF'}); } if (!data.roHref) { var parsed = Hash.parsePadUrl(data.href); - var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password); - data.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret); + 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; } @@ -742,7 +744,7 @@ define([ // Edit > Edit (present) > View > View (present) for (var id in allPads) { var pad = allPads[id]; - if (!pad.href || !pad.roHref) { continue; } + if (!pad.href && !pad.roHref) { continue; } var p2 = Hash.parsePadUrl(pad.href || pad.roHref); var h2 = p2.hashData; @@ -1364,7 +1366,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 630f8aae1..4bc0019c3 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -133,11 +133,6 @@ define([ if (!loggedIn && !config.testMode) { allFilesPaths.forEach(function (path) { var id = path[1]; - /* XXX - var el = exp.find(path); - if (!el) { return; } - var id = exp.getIdFromHref(el.href); - */ if (!id) { return; } spliceFileData(id); }); @@ -256,15 +251,6 @@ define([ }; // REPLACE - /* XXX - 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, 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) { @@ -576,17 +562,18 @@ define([ } // If we have an edit link, check the view link - if (el.href) { - var fixRo = function () { + 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.getViewHasFromKeys(secret); - }; - if (!el.roHref) { - fixRo(); + el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret); } else { var parsed2 = Hash.parsePadUrl(el.roHref); if (!parsed2.hash || !parsed2.type) { - fixRo(); + secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); + el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret); } } } diff --git a/www/common/userObject.js b/www/common/userObject.js index 4d8a6a0a2..e19614363 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -79,13 +79,6 @@ define([ if (!isFile(element)) { return false; } var data = exp.getFileData(element); return Boolean(data.roHref && !data.href); - /* XXX - 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'; - */ }; var isFolder = exp.isFolder = function (element) { diff --git a/www/drive/inner.js b/www/drive/inner.js index 9e538b1f4..fb6f56bf4 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -2691,22 +2691,6 @@ define([ return $div.html(); }; - /* XXX - var getReadOnlyUrl = APP.getRO = function (id) { - if (!filesOp.isFile(id)) { return; } - var data = filesOp.getFileData(id); - if (!data) { return; } - if (data.roHref) { return data.roHref; } - 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; } @@ -2723,20 +2707,14 @@ define([ //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; - data.roHref = base + data.roHref; - - /* XXX - var roUrl; - if (ro) { - data.roHref = data.href; - delete data.href; - } else { - roUrl = getReadOnlyUrl(el); - if (roUrl) { data.roHref = base + roUrl; } + if (!data || !(data.href || data.roHref)) { return void cb('INVALID_FILE'); } + + if (data.href) { + data.href = base + data.href; + } + if (data.roHref) { + data.roHref = base + data.roHref; } - */ UIElements.getProperties(common, data, cb); }; @@ -2810,11 +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(el); - //, roUrl); XXX + 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')) { 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");