From e3f5c893336ffa4a2cf02f9935c802da6c084d66 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 23 Jan 2020 16:11:06 +0100 Subject: [PATCH 01/48] 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 4a2b0fc114d5277aaa443105c861f6a6c0e4e640 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 24 Jan 2020 15:58:42 +0100 Subject: [PATCH 02/48] Allow ooslide and oodoc imports --- www/common/onlyoffice/inner.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 53ff82307..bd08de807 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -1399,10 +1399,18 @@ define([ $exportXLSX.appendTo($rightside); var accept = [".bin", ".ods", ".xlsx"]; + if (type === "ooslide") { + accept = ['.bin', '.odp', '.pptx']; + } else if (type === "oodoc") { + accept = ['.bin', '.odt', '.docx']; + } if (typeof(Atomics) === "undefined") { accept = ['.bin']; } - var $importXLSX = common.createButton('import', true, { accept: accept, binary : ["ods", "xlsx"] }, importXLSXFile); + var $importXLSX = common.createButton('import', true, { + accept: accept, + binary : ["ods", "xlsx", "odt", "docx", "odp", "pptx"] + }, importXLSXFile); $importXLSX.appendTo($rightside); if (common.isLoggedIn()) { From 009bbd69bdbb52e8ca6d04767f0efc865a6d60e5 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 24 Jan 2020 16:25:47 +0100 Subject: [PATCH 03/48] Fix import button in onlyoffice --- www/common/onlyoffice/inner.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index bd08de807..71f43f3a1 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -1398,6 +1398,7 @@ define([ var $exportXLSX = common.createButton('export', true, {}, exportXLSXFile); $exportXLSX.appendTo($rightside); + var type = common.getMetadataMgr().getPrivateData().ooType; var accept = [".bin", ".ods", ".xlsx"]; if (type === "ooslide") { accept = ['.bin', '.odp', '.pptx']; From 1ecb61fe852d45b405c980be3ed3365f3c931dec Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 24 Jan 2020 10:30:23 -0500 Subject: [PATCH 04/48] put an ugly red border on support thread messages from admins --- www/admin/app-admin.less | 3 +++ www/support/ui.js | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/www/admin/app-admin.less b/www/admin/app-admin.less index 10e178308..e9ba67325 100644 --- a/www/admin/app-admin.less +++ b/www/admin/app-admin.less @@ -23,5 +23,8 @@ display: flex; flex-flow: column; } + .cp-support-fromadmin { + border: 1px solid red !important; // XXX + } } diff --git a/www/support/ui.js b/www/support/ui.js index 42ee89c94..c008e219c 100644 --- a/www/support/ui.js +++ b/www/support/ui.js @@ -170,7 +170,9 @@ define([ var privateData = metadataMgr.getPrivateData(); // Check content.sender to see if it comes from us or from an admin - var fromMe = content.sender && content.sender.edPublic === privateData.edPublic; + var senderKey = content.sender && content.sender.edPublic; + var fromMe = senderKey === privateData.edPublic; + var fromAdmin = ctx.adminKeys.indexOf(senderKey) !== -1; var userData = h('div.cp-support-showdata', [ Messages.support_showData, @@ -183,7 +185,7 @@ define([ }); var name = Util.fixHTML(content.sender.name) || Messages.anonymous; - return h('div.cp-support-list-message', { + return h('div.cp-support-list-message' + (fromAdmin? '.cp-support-fromadmin': ''), { 'data-hash': hash }, [ h('div.cp-support-message-from' + (fromMe ? '.cp-support-fromme' : ''), [ @@ -219,6 +221,7 @@ define([ common: common, isAdmin: isAdmin, pinUsage: pinUsage || false, + adminKeys: Array.isArray(ApiConfig.adminKeys)? ApiConfig.adminKeys.slice(): [], }; ui.sendForm = function (id, form, dest) { From 9a53b3b9fd69d03e1f1f2341bcc9b8dec0f3b171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Fri, 24 Jan 2020 16:52:44 +0000 Subject: [PATCH 05/48] style messages - blue text for messages from admins - red text and background for messages needing a response. --- www/admin/app-admin.less | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/www/admin/app-admin.less b/www/admin/app-admin.less index e9ba67325..eb2a8c0f8 100644 --- a/www/admin/app-admin.less +++ b/www/admin/app-admin.less @@ -23,8 +23,26 @@ display: flex; flex-flow: column; } + + .cp-support-list-actions { + margin: 10px 0px 10px 2px; + } + .cp-support-list-message { + &:last-child:not(.cp-support-fromadmin) { + color: @colortheme_cp-red; + background-color: lighten(@colortheme_cp-red, 25%); + .cp-support-showdata { + background-color: lighten(@colortheme_cp-red, 30%); + } + } + } + .cp-support-fromadmin { - border: 1px solid red !important; // XXX + color: @colortheme_logo-2; + background-color: #FFF; + .cp-support-message-content { + color: @colortheme_logo-2; + } } } From 0158ce6804c6c9fdcedec18a4214eaeee15cbc15 Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 27 Jan 2020 10:12:28 +0000 Subject: [PATCH 06/48] Translated using Weblate (Catalan) Currently translated at 48.8% (585 of 1198 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/ca/ --- www/common/translations/messages.ca.json | 60 ++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/www/common/translations/messages.ca.json b/www/common/translations/messages.ca.json index 53e12630f..536f4b1bc 100644 --- a/www/common/translations/messages.ca.json +++ b/www/common/translations/messages.ca.json @@ -12,7 +12,7 @@ "media": "Multimèdia", "todo": "Tasques", "contacts": "Contactes", - "sheet": "Full (Beta)", + "sheet": "Full de càlcul", "teams": "Equips" }, "button_newpad": "Nou document", @@ -34,7 +34,7 @@ "inactiveError": "Donada la seva inactivitat, aquest document s'ha esborrat. Premeu Esc per crear un nou document.", "chainpadError": "Hi ha hagut un error crític mentre s'actualitzava el vostre contingut. Aquesta pàgina es manté en mode només de lectura per assegurar que no perdreu el que ja heu fet.
Premeu Esc per continuar veient aquest document o torneu a carregar la pàgina per provar de continuar editant-lo.", "invalidHashError": "El document que heu demanat té una adreça URL no vàlida.", - "errorCopy": " Encara podeu copiar el contingut en una altra ubicació prement Esc.
Un cop deixeu aquesta pàgina, desapareixerà per sempre!", + "errorCopy": " Encara podeu accedir al contingut prement Esc.
Un cop tanqueu aquesta finestra no hi podreu tornar a accedir.", "errorRedirectToHome": "Premeu Esc per tornar al vostre CryptDrive.", "newVersionError": "Hi ha una nova versió disponible de CryptPad.
Torneu a carregar la pàgina per utilitzar la versió nova o premeu Esc per accedir al vostre contingut en mode fora de línia.", "loading": "Carregant...", @@ -531,5 +531,59 @@ "settings_padSpellcheckTitle": "Correcció ortogràfica", "settings_padSpellcheckHint": "Aquesta opció us permet habilitar la correcció ortogràfica als documents de text. Les errades es subratllaran en vermell i haureu de mantenir apretada la tecla Ctrl o Meta mentre cliqueu el botó dret per veure les opcions correctes.", "settings_padSpellcheckLabel": "Activa la correcció ortogràfica", - "settings_creationSkip": "Salta la pantalla de creació de document" + "settings_creationSkip": "Salta la pantalla de creació de document", + "settings_creationSkipHint": "La pantalla de creació de documents ofereix noves opcions, donant-vos més control sobre les vostres dades. Tot i això, pot alentir una mica la feina afegint un pas addicional i, per això, teniu l'opció de saltar aquesta pantalla i utilitzar les opcions per defecte que hi ha seleccionades.", + "settings_creationSkipTrue": "Salta", + "settings_creationSkipFalse": "Mostra", + "settings_templateSkip": "Salta la finestra de selecció de plantilla", + "settings_templateSkipHint": "Quan genereu un document nou buit, si teniu desades plantilles per aquest tipus de document, apareix una finestra preguntant-vos si voleu utilitzar una plantilla. Aquí podeu triar si no voleu veure mai més la finestra i no utilitzar una plantilla.", + "settings_ownDriveTitle": "Habilita les darreres funcionalitats del compte", + "settings_ownDriveHint": "Per raons tècniques, els comptes antics no tenen accés a totes les funcionalitats noves. Si feu una actualització a un compte nou, preparareu el vostre CryptDrive per les properes funcionalitats sense interrompre la vostra activitat habitual.", + "settings_ownDriveButton": "Milloreu el vostre compte", + "settings_ownDriveConfirm": "Millorar el vostre compte porta una estona. Necessitareu tornar-vos a connectar en tots els vostres dispositius. Segur que ho voleu fer?", + "settings_ownDrivePending": "El vostre compte s'està posant al dia. No tanqueu ni torneu a carregar aquesta pàgina fins que el procés hagi acabat.", + "settings_changePasswordTitle": "Canvieu la contrasenya", + "settings_changePasswordHint": "Canvieu la contrasenya del vostre compte. Introduïu la contrasenya actual i confirmeu la nova escrivint-la dos cops.
Si l'oblideu, no podem recuperar la vostra contrasenya, aneu amb molt de compte!", + "settings_changePasswordButton": "Canvia la contrasenya", + "settings_changePasswordCurrent": "Contrasenya actual", + "settings_changePasswordNew": "Nova contrasenya", + "settings_changePasswordNewConfirm": "Confirma la nova contrasenya", + "settings_changePasswordConfirm": "Segur que voleu canviar la contrasenya? Necessitareu tornar-vos a connectar en tots els dispositius.", + "settings_changePasswordError": "Hi ha hagut una errada inesperada. Si no podeu iniciar la sessió o canviar la contrasenya, contacteu l'administració de CryptPad.", + "settings_changePasswordPending": "S'està actualitzant la contrasenya. Si us plau, no tanqueu ni carregueu de nou la pàgina fins que el procés s'hagi acabat.", + "settings_changePasswordNewPasswordSameAsOld": "La contrasenya nova cal que sigui diferent de l'actual.", + "settings_cursorColorTitle": "Color del cursor", + "settings_cursorColorHint": "Canvieu el color associat al vostre compte en els documents col·laboratius.", + "settings_cursorShareTitle": "Comparteix la posició del meu cursor", + "settings_cursorShareHint": "Podeu decidir si, als documents col·laboratius, voleu que la resta de persones vegin el vostre cursor.", + "settings_cursorShareLabel": "Comparteix la posició", + "settings_cursorShowTitle": "Mostra la posició del cursor de la resta", + "settings_cursorShowHint": "Podeu triar si, als documents col·laboratius, voleu veure el cursor de les altres persones.", + "settings_cursorShowLabel": "Mostra els cursors", + "upload_title": "Carrega fitxer", + "upload_type": "Tipus", + "upload_modal_title": "Opcions per carregar fitxers", + "upload_modal_filename": "Nom del fitxer (extensió {0} afegit automàticament)", + "upload_modal_owner": "Fitxer propi", + "uploadFolder_modal_title": "Opcions per carregar carpetes", + "uploadFolder_modal_filesPassword": "Fitxers de contrasenya", + "uploadFolder_modal_owner": "Fitxers propis", + "uploadFolder_modal_forceSave": "Deseu fitxers al vostre CryptDrive", + "upload_serverError": "Errada interna: ara mateix és impossible carregar el fitxer.", + "upload_uploadPending": "Ja teniu una càrrega en marxa. Voleu cancel·lar-la i carregar aquest altre fitxer?", + "upload_success": "El fitxer ({0}) ha estat carregat correctament i afegit al vostre CryptDrive.", + "upload_notEnoughSpace": "No hi ha prou espai al CryptDrive per aquest fitxer.", + "upload_notEnoughSpaceBrief": "No hi ha prou espai", + "upload_tooLarge": "Aquest fitxer supera la mida màxima permesa.", + "upload_tooLargeBrief": "El fitxer és massa gran", + "upload_choose": "Trieu un fitxer", + "upload_pending": "Pendent", + "upload_cancelled": "Cancel·lat", + "upload_name": "Nom del fitxer", + "upload_size": "Mida", + "upload_progress": "Procés", + "upload_mustLogin": "Cal que inicieu la sessió per carregar un fitxer", + "upload_up": "Carrega", + "download_button": "Desxifra i descarrega", + "download_mt_button": "Descarrega" } From 66ef508e0e7f784bfc8e70b3bb5ec4a098b71c9a Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 27 Jan 2020 10:12:29 +0000 Subject: [PATCH 07/48] Translated using Weblate (English) Currently translated at 100.0% (1199 of 1199 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ --- www/common/translations/messages.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json index d728f8742..62cb7a696 100644 --- a/www/common/translations/messages.json +++ b/www/common/translations/messages.json @@ -1294,5 +1294,6 @@ "oo_exportInProgress": "Export in progress", "oo_sheetMigration_loading": "Upgrading your spreadsheet to the latest version", "oo_sheetMigration_complete": "Updated version available, press OK to reload.", - "oo_sheetMigration_anonymousEditor": "Editing this spreadsheet is disabled for anonymous users until it is upgraded to the latest version by a registered user." + "oo_sheetMigration_anonymousEditor": "Editing this spreadsheet is disabled for anonymous users until it is upgraded to the latest version by a registered user.", + "imprint": "Legal notice" } From b6a6249eb44056c558ad6c5b43d440a5aac86c43 Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 27 Jan 2020 10:12:29 +0000 Subject: [PATCH 08/48] Translated using Weblate (Finnish) Currently translated at 71.3% (855 of 1199 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/fi/ Translated using Weblate (Finnish) Currently translated at 71.4% (855 of 1198 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/fi/ --- www/common/translations/messages.fi.json | 147 ++++++++++++++++++++++- 1 file changed, 145 insertions(+), 2 deletions(-) diff --git a/www/common/translations/messages.fi.json b/www/common/translations/messages.fi.json index 365ea28b8..1df2e4d81 100644 --- a/www/common/translations/messages.fi.json +++ b/www/common/translations/messages.fi.json @@ -11,7 +11,7 @@ "media": "Media", "todo": "Tehtävälista", "contacts": "Yhteystiedot", - "sheet": "Taulukko (Beta)", + "sheet": "Taulukko", "teams": "Teams" }, "button_newpad": "Uusi Teksti-padi", @@ -876,8 +876,151 @@ "keywords": { "title": "Avainsanat", "pad": { - "q": "Mikä on padi?" + "q": "Mikä on padi?", + "a": "Padi on Etherpad-projektin popularisoima termi reaaliaikaiselle kollaboratiiviselle editorille.\nSe tarkoittaa selaimessa muokattavaa dokumenttia, jossa muiden käyttäjien tekemät muutokset näkyvät lähes välittömästi." + }, + "owned": { + "q": "Mikä on omistettu padi?", + "a": "Omistettu padi on padi, jolla on erityisesti määritelty omistaja, jonka palvelin tunnistaa julkisen salausavaimen perusteella. Padin omistaja voi poistaa omistamansa padit palvelimelta, jolloin muut yhteiskäyttäjät eivät voi enää käyttää niitä riippumatta siitä, olivatko ne tallennettuna heidän henkilökohtaisiin CryptDriveihinsa." + }, + "expiring": { + "q": "Mikä on vanheneva padi?", + "a": "Vanheneva padi on padi, jolle on määritelty vanhenemisajankohta, jolloin padi poistetaan automaattisesti palvelimelta. Vanhenevat padit voidaan määritellä säilymään minkä tahansa ajan yhdestä tunnista 100 kuukauteen. Vanheneva padi ja sen historia muuttuvat vanhenemishetkellä pysyvästi käyttökelvottomiksi, vaikka padia muokattaisiinkin silloin.

Jos padi on määritelty vanhenevaksi, voit tarkastaa sen vanhenemisajan padin ominaisuuksista joko CryptDrivessa padin kohdalla hiiren oikealla painikkeella aukeavasta valikosta tai käyttämällä Ominaisuudet-valikkoa sovelluksen työkalupalkista." + }, + "tag": { + "q": "Miten voin käyttää tunnisteita?", + "a": "Voit lisätä padeihin ja ladattuihin tiedostoihin tunnisteita CryptDrivessa tai käyttää Tunniste-painiketta () minkä tahansa editorin työkalupalkista. Hae padeja ja tiedostoja CryptDriven hakupalkista käyttämällä ristikkomerkillä alkavaa hakusanaa (esimerkiksi #crypto)." + }, + "template": { + "q": "Mikä on mallipohja?", + "a": "Mallipohja on padi, jolla voit määritellä luotavan padin oletussisällön luodessasi toista samantyyppistä padia. Voit muuttaa minkä tahansa olemassaolevan padin mallipohjaksi siirtämällä sen Mallipohjat-osastoon CryptDrivessasi. Voit myös tehdä padista mallipohjana käytettävän kopion klikkaamalla Mallipohja-painiketta () editorin työkalupalkista." + }, + "abandoned": { + "q": "Mikä on hylätty padi?", + "a": "Hylätty padi on padi, jota ei ole kiinnitetty yhdenkään rekisteröityneen käyttäjän CryptDriveen ja jota ei ole muokattu kuuteen kuukauteen. Hylätyt dokumentit poistetaan palvelimelta automaattisesti." + } + }, + "privacy": { + "title": "Yksityisyys", + "different": { + "q": "Miten CryptPad eroaa muista padeja tarjoavista palveluista?", + "a": "CryptPad salaa padeihin tekemäsi muutokset ennen niiden lähettämistä palvelimelle, joten emme voi lukea, mitä kirjoitat." + }, + "me": { + "q": "Mitä palvelin tietää minusta?", + "a": "Palvelimen ylläpitäjät näkevät CryptPadia käyttävien ihmisten IP-osoitteet. Emme pidä kirjaa siitä, mitkä osoitteet vierailevat missäkin padeissa. Tämä olisi kuitenkin teknisesti mahdollista, vaikka emme pääsekään tarkastelemaan padien salaamatonta sisältöä. Jos pelkäät meidän analysoivan näitä tietoja, on parasta olettaa meidän keräävän niitä, sillä emme voi todistaa, ettemme tee niin.

Keräämme käyttäjiltämme joitakin perustason telemetriatietoja, kuten käytetyn laitteen näytön koon ja tietoja useimmin käytetyistä painikkeista. Nämä auttavat meitä parantamaan CryptPadia, mutta jos et halua lähettää telemetriatietoja CryptPadille, voit jättäytyä pois tietojen keräämisestä ottamalla rastin pois Salli käyttäjäpalaute-ruudusta.

Pidämme kirjaa siitä, mitä padeja käyttäjät säilyttävät CryptDriveissaan pystyäksemme asettamaan tallennustilarajoituksia. Emme kuitenkaan tiedä näiden padien tyyppiä tai sisältöä. Tallennustilakiintiöt määritellään käyttäjien julkisten salausavainten perusteella, mutta emme yhdistä käyttäjien nimiä tai sähköpostiosoitteita näihin avaimiin.

Saadaksesi lisätietoja aiheesta voit tutustua kirjoittamaamme blogikirjoitukseen." + }, + "register": { + "q": "", + "a": "" + }, + "other": { + "q": "", + "a": "" + }, + "anonymous": { + "q": "", + "a": "" + }, + "policy": { + "q": "", + "a": "" + } + }, + "security": { + "pad_password": { + "q": "Mitä tapahtuu, kun suojaan padin tai kansion salasanalla?", + "a": "Voit suojata minkä tahansa padin tai jaetun kansion salasanalla luodessasi sen. Voit myös käyttää Ominaisuudet-valikkoa asettaaksesi, vaihtaaksesi tai poistaaksesi salasanan milloin tahansa.

Padien ja jaettujen kansioiden salasanat on tarkoitettu suojaamaan linkkiä jakaessasi sitä mahdollisesti turvattomien kanavien, kuten sähköpostin tai tekstiviestin kautta. Jos joku onnistuu kaappaamaan linkkisi, mutta ei tiedä sen salasanaa, ei hän pääse lukemaan dokumenttiasi.

Kun jaat sisältöä CryptPadin sisällä yhteystietojesi tai tiimiesi kanssa, tiedonsiirto on salattua ja oletamme, että haluat heidän pääsevän käyttämään dokumenttiasi. Siksi salasana tallennetaan ja lähetetään padin mukana jakaessasi sitä CryptPadin sisällä. Vastaanottajalta tai sinulta itseltäsi ei pyydetä salasanaa dokumenttia avatessa." + }, + "title": "", + "proof": { + "q": "", + "a": "" + }, + "why": { + "q": "", + "a": "" + }, + "compromised": { + "q": "", + "a": "" + }, + "crypto": { + "q": "", + "a": "" + } + }, + "usability": { + "title": "Käytettävyys", + "register": { + "q": "Mitä hyötyä rekisteröitymisestä on minulle?", + "a": "Rekisteröityneille käyttäjille on tarjolla joitakin toimintoja, jotka eivät ole saatavilla rekisteröitymättömille käyttäjille. Löydät nämä toiminnot luomastamme kaaviosta." + }, + "share": { + "q": "Miten jaan salattuja padeja kavereideni kanssa?", + "a": "CryptPad laittaa URL-osoitteessa padisi salaisen salausavaimen #-merkin jälkeen. Tämän merkin jälkeen laitettuja tietoja ei lähetetä palvelimelle, joten emme pääse koskaan käyttämään salausavaimiasi. Jakaessasi linkin padiin jaat oikeuden lukea ja käyttää sitä." + }, + "remove": { + "q": "Poistin padin tai tiedoston CryptDrivestani, mutta sen sisältö on yhä käytettävissä. Miten voin poistaa sen?", + "a": "Ainoastaan omistettuja padeja (otettu käyttöön helmikuussa 2018) voi poistaa. Lisäksi nämä padit voi poistaa ainoastaan niiden omistaja eli henkilö, joka alun perin loi kyseisen padin. Jos et ole luonut kyseistä padia, joudut pyytämään sen omistajaa poistamaan sen puolestasi. Omistamiesi padien poistaminen onnistuu CryptDrivessa klikkaamalla padia hiiren oikealla painikkeella ja valitsemalla Poista palvelimelta." + }, + "forget": { + "q": "Mitä tapahtuu, jos unohdan salasanani?", + "a": "Valitettavasti se, että pystyisimme palauttamaan käyttöoikeuden salattuihin padeihisi tarkoittaisi myös sitä, että pääsisimme itse käsiksi niiden sisältöön. Jos et kirjoittanut käyttäjätunnustasi ja salasanaasi ylös etkä muista kumpaakaan, voit mahdollisesti palauttaa padisi selaimesi historiaa suodattamalla." + }, + "change": { + "q": "Entä jos haluan vaihtaa salasanani?", + "a": "Voit vaihtaa CryptPad-salasanasi Tilin asetukset-sivulta." + }, + "devices": { + "q": "Olen kirjautunut sisään kahdella laitteella, ja näen kaksi eri CryptDrivea. Miten tämä on mahdollista?", + "a": "On todennäköistä, että olet rekisteröitynyt samalla käyttäjänimellä kahdesti eri salasanoja käyttäen. CryptPad-palvelin tunnistaa sinut kryptografisen allekirjoituksesi perusteella käyttäjänimen sijaan, joten se ei voi estää muita rekisteröitymästä samalla käyttäjänimellä. Tästä johtuen jokaisella käyttäjätilillä on ainutlaatuinen käyttäjänimen ja salasanan yhdistelmä. Sisäänkirjautuneet käyttäjät voivat nähdä käyttäjänimensä Asetukset-sivun ylälaidassa." + }, + "folder": { + "q": "Voinko jakaa kokonaisia kansioita CryptDrivestani?", + "a": "Kyllä, voit luoda jaetun kansion CryptDrivestasi ja jakaa kerralla kaikki sen sisältämät padit." + }, + "feature": { + "q": "Voitteko lisätä CryptPadiin tarvitsemani ominaisuuden?", + "a": "Monet CryptPadin ominaisuuksista ovat olemassa, koska käyttäjämme ovat toivoneet niitä. Yhteystiedot-sivumme kertoo, millä tavoin meihin saa yhteyden.

Valitettavasti emme voi taata, että pystymme toteuttamaan kaikki käyttäjiemme ehdotukset. Jos jokin tietty ominaisuus on kriittinen organisaatiosi kannalta, voit sponsoroida kehitystä varmistaaksesi sen toteutumisen. Ota yhteyttä osoitteeseen sales@cryptpad.fr saadaksesi lisätietoja.

Vaikka kehitystyön sponsorointi ei olisikaan mahdollista, olemme silti kiinnostuneita palautteesta, joka auttaa meitä parantamaan CryptPadia. Ota meihin milloin tahansa yhteyttä yllä luetelluilla tavoilla." + } + }, + "other": { + "title": "Muita kysymyksiä", + "pay": { + "q": "Miksi minun täytyisi maksaa, kun niin monet toiminnot ovat ilmaisia?", + "a": "Annamme tukijoillemme lisätallennustilaa ja mahdollisuuden kasvattaa kavereiden tallennustilakiintiöitä (lue lisää).

Näiden lyhytaikaisten etujen lisäksi premium-tilaus auttaa rahoittamaan CryptPadin jatkuvaa, aktiivista kehitystyötä. Tähän kuuluu bugien korjaamista, uusien ominaisuuksien lisäämistä ja CryptPad-instanssien pystyttämisen ja ylläpidon helpottamista. Lisäksi autat näyttämään muille palveluntarjoajille, että ihmiset ovat valmiita tukemaan yksityisyyttä parantavia teknologioita. Toivomme, että käyttäjätietojen myymiseen perustuvat liiketoimintamallit jäävät lopulta menneeseen.

Lopuksi, tarjoamme suurimman osan CryptPadin toiminnallisuudesta ilmaiseksi, koska uskomme yksityisyyden kuuluvan kaikille - ei vain niille, joilla on varaa maksaa siitä. Tukemalla meitä autat tarjoamaan heikommassa asemassa oleville väestöille pääsyn näihin peruspalveluihin." + }, + "goal": { + "q": "Mitkä ovat tavoitteenne?", + "a": "Kehittämällä yksityisyyttä kunnioittavaa kollaboraatioteknologiaa toivomme nostavamme käyttäjien odotuksia pilvipalveluiden yksityisyyden suhteen. Toivomme, että työmme rohkaisee muita palveluntarjoajia pyrkimään samaan tai parempaan lopputulokseen. Optimismistamme huolimatta tiedämme, että suuri osa webistä rahoitetaan kohdistetulla mainonnalla. Tehtävää on paljon enemmän, kuin mihin pystymme yksin - arvostamme yhteisömme tarjoamaa mainostusta, tukea ja panosta tavoitteidemme saavuttamisessa." + }, + "jobs": { + "q": "Etsittekö työntekijöitä?", + "a": "Kyllä! Esittäydy meille sähköpostilla osoitteeseen jobs@xwiki.com." + }, + "host": { + "q": "Voitteko auttaa minua perustamaan oman CryptPad-instanssini?", + "a": "Tarjoamme mielellämme tukea organisaatiosi sisäiselle CryptPad-instanssille. Ota yhteyttä osoitteeseen sales@cryptpad.fr saadaksesi lisätietoja." + }, + "revenue": { + "q": "Kuinka voin osallistua tulojen jakamiseen?", + "a": "Jos ylläpidät omaa CryptPad-instanssiasi, haluaisit ottaa käyttöön maksulliset käyttäjätilit ja jakaa tulot CryptPadin kehittäjien kanssa, palvelimesi täytyy määritellä kumppanipalveluksi.

CryptPad-asennushakemistosi config.example.js-tiedostosta pitäisi löytyä ohjeet tämän palvelun käyttöönottoon. Sinun tulee myös ottaa yhteyttä osoitteeseen sales@cryptpad.fr varmistaaksesi, että palvelimesi HTTPS-määritykset ovat kunnossa ja sopiaksesi käytettävistä maksutavoista." } } + }, + "policy_howweuse_p1": "Käytämme näitä tietoja suunnitellaksemme CryptPadin mainostusta ja arvioidaksemme aiempien kampanjoiden onnistumista. Sijaintitietosi puolestaan kertovat meille, mitä kieliä CryptPadin tulisi mahdollisesti tukea englannin lisäksi.", + "tos_title": "", + "tos_legal": "", + "tos_availability": "", + "tos_e2ee": "", + "tos_logs": "", + "tos_3rdparties": "", + "four04_pageNotFound": "", + "updated_0_header_logoTitle": "", + "header_logoTitle": "", + "header_homeTitle": "", + "help": { + "title": "" } } From 6f6bbaf75ecc5c9387001f47d1b672cdfcba364e Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 27 Jan 2020 10:12:29 +0000 Subject: [PATCH 09/48] Translated using Weblate (French) Currently translated at 100.0% (1199 of 1199 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/fr/ --- www/common/translations/messages.fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json index 014837192..9abea1c41 100644 --- a/www/common/translations/messages.fr.json +++ b/www/common/translations/messages.fr.json @@ -1294,5 +1294,6 @@ "oo_exportInProgress": "Exportation en cours", "oo_sheetMigration_loading": "Mise à jour de la feuille de calcul", "oo_sheetMigration_complete": "Version mise à jour disponible, appuyez sur OK pour recharger.", - "oo_sheetMigration_anonymousEditor": "L'édition de cette feuille de calcul est désactivée pour les utilisateurs anonymes jusqu'à ce qu'elle soit mise à jour par un utilisateur enregistré." + "oo_sheetMigration_anonymousEditor": "L'édition de cette feuille de calcul est désactivée pour les utilisateurs anonymes jusqu'à ce qu'elle soit mise à jour par un utilisateur enregistré.", + "imprint": "Mentions légales" } From 27f864128d745e88b1e4af023af28974a294db68 Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 27 Jan 2020 10:12:30 +0000 Subject: [PATCH 10/48] Translated using Weblate (German) Currently translated at 100.0% (1199 of 1199 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/de/ Translated using Weblate (German) Currently translated at 100.0% (1199 of 1199 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/de/ Translated using Weblate (German) Currently translated at 100.0% (1199 of 1199 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/de/ --- www/common/translations/messages.de.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json index 679252b23..6837b3d8e 100644 --- a/www/common/translations/messages.de.json +++ b/www/common/translations/messages.de.json @@ -12,7 +12,7 @@ "media": "Medien", "todo": "Aufgaben", "contacts": "Kontakte", - "sheet": "Tabelle (Beta)", + "sheet": "Tabelle", "teams": "Teams" }, "button_newpad": "Neues Rich-Text-Pad", @@ -92,7 +92,7 @@ "exportButtonTitle": "Exportiere dieses Pad in eine lokale Datei", "exportPrompt": "Wie möchtest du die Datei nennen?", "changeNamePrompt": "Ändere deinen Namen (oder lasse dieses Feld leer, um anonym zu bleiben): ", - "user_rename": "Bearbeite deinen Anzeigename", + "user_rename": "Anzeigename ändern", "user_displayName": "Anzeigename", "user_accountName": "Kontoname", "clickToEdit": "Zum Bearbeiten klicken", @@ -278,8 +278,8 @@ "userlist_addAsFriendTitle": "Benutzer \"{0}\" eine Freundschaftsanfrage senden", "contacts_title": "Kontakte", "contacts_addError": "Fehler bei dem Hinzufügen des Kontakts zur Liste", - "contacts_added": "Verbindungseinladung angenommen.", - "contacts_rejected": "Verbindungseinladung abgelehnt", + "contacts_added": "Kontaktanfrage akzeptiert.", + "contacts_rejected": "Kontaktanfrage abgelehnt", "contacts_request": "Benutzer {0} möchte dich als Kontakt hinzufügen. Annehmen?", "contacts_send": "Senden", "contacts_remove": "Diesen Kontakt entfernen", @@ -1100,7 +1100,7 @@ "support_formMessage": "Gib deine Nachricht ein...", "support_cat_tickets": "Vorhandene Tickets", "support_listTitle": "Support-Tickets", - "support_listHint": "Hier ist die Liste der an die Administratoren gesendeten Tickets und der dazugehörigen Antworten. Ein geschlossenes Ticket kann nicht wieder geöffnet werden, du musst ein Ticket eröffnen. Du kannst geschlossene Tickets ausblenden, aber sie werden weiterhin für die Administratoren sichtbar sein.", + "support_listHint": "Hier ist die Liste der an die Administratoren gesendeten Tickets und der dazugehörigen Antworten. Ein geschlossenes Ticket kann nicht wieder geöffnet werden, du kannst jedoch ein neues Ticket eröffnen. Du kannst geschlossene Tickets ausblenden, aber sie werden weiterhin für die Administratoren sichtbar sein.", "support_answer": "Antworten", "support_close": "Ticket schließen", "support_remove": "Ticket entfernen", @@ -1294,5 +1294,6 @@ "oo_exportInProgress": "Export wird durchgeführt", "oo_sheetMigration_loading": "Deine Tabelle wird auf die neueste Version aktualisiert", "oo_sheetMigration_complete": "Eine aktualisierte Version ist verfügbar. Klicke auf OK, um neu zu laden.", - "oo_sheetMigration_anonymousEditor": "Die Bearbeitung dieser Tabelle ist für anonyme Benutzer deaktiviert, bis sie von einem registrierten Benutzer auf die neueste Version aktualisiert wird." + "oo_sheetMigration_anonymousEditor": "Die Bearbeitung dieser Tabelle ist für anonyme Benutzer deaktiviert, bis sie von einem registrierten Benutzer auf die neueste Version aktualisiert wird.", + "imprint": "Impressum" } From b8ec7178da162c5dcc8f0942ab2eee9fd7c4b779 Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 27 Jan 2020 10:12:30 +0000 Subject: [PATCH 11/48] Translated using Weblate (Italian) Currently translated at 41.7% (499 of 1198 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/it/ --- www/common/translations/messages.it.json | 42 +++++++++++++----------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/www/common/translations/messages.it.json b/www/common/translations/messages.it.json index 4d8b28b5d..2a8211f03 100644 --- a/www/common/translations/messages.it.json +++ b/www/common/translations/messages.it.json @@ -1,8 +1,8 @@ { - "main_title": "CryptPad: Editor collaborativo in tempo reale, zero knowledge", + "main_title": "CryptPad: Editor zero knowledge collaborativo in tempo reale", "type": { "pad": "Testo", - "code": "Code", + "code": "Codice", "poll": "Sondaggio", "kanban": "Kanban", "slide": "Presentazione", @@ -10,13 +10,13 @@ "whiteboard": "Lavagna", "file": "File", "media": "Media", - "todo": "Todo", + "todo": "Promemoria", "contacts": "Contatti", - "sheet": "Fogli (Beta)", + "sheet": "Fogli", "teams": "Team" }, "button_newpad": "Nuovo pad di Testo", - "button_newcode": "Nuovo pad di Code", + "button_newcode": "Nuovo pad di Codice", "button_newpoll": "Nuovo Sondaggio", "button_newslide": "Nuova Presentazione", "button_newwhiteboard": "Nuova Lavagna", @@ -34,7 +34,7 @@ "inactiveError": "Questo pad è stato cancellato per inattività. Premi Esc per creare un nuovo pad.", "chainpadError": "Si è verificato un errore critico nell'aggiornamento del tuo contenuto. Questa pagina è in modalità solo lettura per assicurarci che non perderai il tuo lavoro..
Premi Esc per continuare a visualizzare questo pad, o ricarica la pagina per provare a modificarlo di nuovo.", "invalidHashError": "Il documento richiesto ha un URL non valido.", - "errorCopy": " Puoi ancora copiare il contenuto altrove premendo Esc.
Una volta abbandonata questa pagina, non sarà possibile recuperarlo!", + "errorCopy": " Puoi ancora accedere al contenuto premendo Esc.
Una volta chiusa questa finestra, non sarà possibile accedere di nuovo.", "errorRedirectToHome": "Premi Esc per essere reindirizzato al tuo CryptDrive.", "newVersionError": "Una nuova versione di CryptPad è disponibile.
Ricarica per usare la nuova versione, o premi Esc per accedere al contenuto in modalità offline.", "loading": "Caricamento...", @@ -447,19 +447,19 @@ "settings_exportTitle": "Esporta il tuo CryptDrive", "settings_exportDescription": "Per favore attendi mentre scarichiamo e decriptiamo i tuoi documenti. Potrebbe richiedere qualche minuto. Chiudere la finestra interromperà il processo.", "settings_exportFailed": "Se il pad richiede più di un minuto per essere scaricato, non sarà incluso nell'export. Un link a qualsiasi pad non esportato sarà mostrato.", - "settings_exportWarning": "", - "settings_exportCancel": "", - "settings_export_reading": "", - "settings_export_download": "", - "settings_export_compressing": "", - "settings_export_done": "", - "settings_exportError": "", - "settings_exportErrorDescription": "", - "settings_exportErrorEmpty": "", - "settings_exportErrorMissing": "", - "settings_exportErrorOther": "", + "settings_exportWarning": "Nota bene: questo strumento è ancora in versione beta e può presentare problemi di scalabilità. Per migliorare le prestazioni, è consigliabile lasciare attiva questa tab.", + "settings_exportCancel": "Sei sicuro di voler cancellare l'export? Dovrai iniziare da capo la prossima volta.", + "settings_export_reading": "Lettura del tuo CryptDrive in corso...", + "settings_export_download": "Scaricamento e decriptazione dei tuoi documenti in corso...", + "settings_export_compressing": "Compressione dei dati in corso...", + "settings_export_done": "Il tuo download è pronto!", + "settings_exportError": "Visualizza errori", + "settings_exportErrorDescription": "Non siamo riusciti ad aggiungere i seguenti documenti all'export:", + "settings_exportErrorEmpty": "Questo documento non può essere esportato (contenuto vuoto o invalido).", + "settings_exportErrorMissing": "Questo documento non è stato trovato nei nostri server (scaduto o rimosso dal suo proprietario)", + "settings_exportErrorOther": "È accaduto un errore durante l'esportazione di questo documento: {0}", "settings_resetNewTitle": "Pulisci CryptDrive", - "settings_resetButton": "", + "settings_resetButton": "Rimuovi", "settings_reset": "Rimuovi tutti i file e le cartelle dal tuo CryptDrive", "settings_resetPrompt": "", "settings_resetDone": "", @@ -513,5 +513,9 @@ }, "readme_cat3_l1": "Con l'editor di codice di CryptPad, puoi collaborare su linguaggi di programmazione come Javascript e linguaggi di markup come HTML o Markdown", "settings_codeSpellcheckLabel": "Abilita la revisione ortografica nell'editor di codice", - "team_inviteLinkError": "Si è verificato un errore durante la creazione del link." + "team_inviteLinkError": "Si è verificato un errore durante la creazione del link.", + "register_emailWarning1": "Puoi farlo se vuoi, ma non verrà inviato ai nostri server.", + "register_emailWarning2": "Non sarai in grado di resettare la tua password usando la tua email, a differenza di come puoi fare con molti altri servizi.", + "register_emailWarning3": "Se hai capito, ma intendi comunque usare la tua email come nome utente, clicca OK.", + "oo_sheetMigration_anonymousEditor": "Le modifiche da parte di utenti anonimi a questo foglio di calcolo sono disabilitate finchè un utente registrato non lo aggiorna all'ultima versione." } From 0ad96e0966faf660cc3ba8a62736b3e370e2f44a Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Jan 2020 12:18:25 +0100 Subject: [PATCH 12/48] 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 13/48] 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 14/48] 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 15/48] Use version 3 for hidden hashes --- www/common/common-hash.js | 91 ++++++++++++++----------------- www/common/sframe-common-outer.js | 2 +- 2 files changed, 43 insertions(+), 50 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]; - - // 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]; - } + parsed.key = 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(); + + 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); + 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 beaea7bb740ded17ccf4fde7b1f19900517bdbfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Mon, 27 Jan 2020 14:05:15 +0000 Subject: [PATCH 16/48] change friends to contacts --- www/common/translations/messages.json | 36 +++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json index 62cb7a696..15dbe5184 100644 --- a/www/common/translations/messages.json +++ b/www/common/translations/messages.json @@ -282,7 +282,7 @@ "profile_description": "Description", "profile_fieldSaved": "New value saved: {0}", "profile_viewMyProfile": "View my profile", - "userlist_addAsFriendTitle": "Send \"{0}\" a friend request", + "userlist_addAsFriendTitle": "Send \"{0}\" a contact request", "contacts_title": "Contacts", "contacts_addError": "Error while adding that contact to the list", "contacts_added": "Contact invite accepted.", @@ -302,7 +302,7 @@ "contacts_confirmRemoveHistory": "Are you sure you want to permanently remove your chat history? Data cannot be restored", "contacts_removeHistoryServerError": "There was an error while removing your chat history. Try again later", "contacts_fetchHistory": "Retrieve older history", - "contacts_friends": "Friends", + "contacts_friends": "Contacts", "contacts_rooms": "Rooms", "contacts_leaveRoom": "Leave this room", "contacts_online": "Another user from this room is online", @@ -689,7 +689,7 @@ "features_f_social": "Social applications", "features_f_social_note": "Create a profile, use an avatar, chat with contacts", "features_f_file1": "Upload and share files", - "features_f_file1_note": "Share files with your friends or embed them in your pads", + "features_f_file1_note": "Share files with your contacts or embed them in your pads", "features_f_storage1": "Permanent storage (50MB)", "features_f_storage1_note": "Pads stored in your CryptDrive are never deleted for inactivity", "features_f_register": "Register for free", @@ -792,7 +792,7 @@ "a": "Registered users have access to a number of features unavailable to unregistered users. There's a chart here." }, "share": { - "q": "How can I share encrypted pads with my friends?", + "q": "How can I share encrypted pads with my contacts?", "a": "CryptPad puts the secret encryption key to your pad after the # character in the URL. Anything after this character is not sent to the server, so we never have access to your encryption keys. By sharing the link to a pad, you share the ability to read and access it." }, "remove": { @@ -824,7 +824,7 @@ "title": "Other questions", "pay": { "q": "Why should I pay when so many features are free?", - "a": "We give supporters additional storage and the ability to increase their friends' quotas (learn more).

Beyond these short term benefits, by subscribing with a premium account you help to fund continued, active development of CryptPad. That includes fixing bugs, adding new features, and making it easier for others to help host CryptPad themselves. Additionally, you help to prove to other service providers that people are willing to support privacy enhancing technologies. It is our hope that eventually business models based on selling user data will become a thing of the past.

Finally, we offer most of CryptPad's functionality for free because we believe everyone deserves personal privacy, not just those with disposable income. By supporting us, you help us continue to make it possible for underprivileged populations to access these basic features without a price tag attached." + "a": "We give supporters additional storage and the ability to increase their contacts' quotas (learn more).

Beyond these short term benefits, by subscribing with a premium account you help to fund continued, active development of CryptPad. That includes fixing bugs, adding new features, and making it easier for others to help host CryptPad themselves. Additionally, you help to prove to other service providers that people are willing to support privacy enhancing technologies. It is our hope that eventually business models based on selling user data will become a thing of the past.

Finally, we offer most of CryptPad's functionality for free because we believe everyone deserves personal privacy, not just those with disposable income. By supporting us, you help us continue to make it possible for underprivileged populations to access these basic features without a price tag attached." }, "goal": { "q": "What is your goal?", @@ -885,7 +885,7 @@ "colors": "Change the text and background colors using the and buttons" }, "poll": { - "decisions": "Make decisions in private among trusted friends", + "decisions": "Make decisions in private among trusted contacts", "options": "Propose options, and express your preferences", "choices": "Click cells in your column to cycle through yes (), maybe (~), or no ()", "submit": "Click submit to make your choices visible to others" @@ -903,7 +903,7 @@ }, "driveReadmeTitle": "What is CryptPad?", "readme_welcome": "Welcome to CryptPad !", - "readme_p1": "Welcome to CryptPad, this is where you can take note of things alone and with friends.", + "readme_p1": "Welcome to CryptPad, this is where you can take note of things alone and with contacts.", "readme_p2": "This pad will give you a quick walk through of how you can use CryptPad to take notes, keep them organized and work together on them.", "readme_cat1": "Get to know your CryptDrive", "readme_cat1_l1": "Make a pad: In your CryptDrive, click {0} then {1} and you can make a pad.", @@ -1075,17 +1075,17 @@ "friendRequest_later": "Decide later", "friendRequest_accept": "Accept (Enter)", "friendRequest_decline": "Decline", - "friendRequest_declined": "{0} declined your friend request", - "friendRequest_accepted": "{0} accepted your friend request", - "friendRequest_received": "{0} would like to be your friend", - "friendRequest_notification": "{0} sent you a friend request", + "friendRequest_declined": "{0} declined your contact request", + "friendRequest_accepted": "{0} accepted your contact request", + "friendRequest_received": "{0} would like to be your contact", + "friendRequest_notification": "{0} sent you a contact request", "notifications_empty": "No notifications available", "notifications_title": "You have unread notifications", "profile_addDescription": "Add a description", "profile_editDescription": "Edit your description", "profile_addLink": "Add a link to your website", "profile_info": "Other users can find your profile through your avatar in document user lists.", - "profile_friendRequestSent": "Friend request pending...", + "profile_friendRequestSent": "Contact request pending...", "profile_friend": "{0} is your friend", "notification_padShared": "{0} has shared a pad with you: {1}", "notification_fileShared": "{0} has shared a file with you: {1}", @@ -1097,7 +1097,7 @@ "share_withFriends": "Share", "notifications_dismiss": "Dismiss", "fm_info_sharedFolderHistory": "This is only the history of your shared folder: {0}
Your CryptDrive will stay in read-only mode while you navigate.", - "share_description": "Choose what you'd like to share and either get the link or send it directly to your CryptPad friends.", + "share_description": "Choose what you'd like to share and either get the link or send it directly to your CryptPad contacts.", "supportPage": "Support", "admin_cat_support": "Support", "admin_supportInitHelp": "Your server is not yet configured to have a support mailbox. If you want a support mailbox to receive messages from your users, you should ask your server administrator to run the script located in \"./scripts/generate-admin-keys.js\", then store the public key in the \"config.js\" file and send you the private key.", @@ -1130,7 +1130,7 @@ "notificationsPage": "Notifications", "openNotificationsApp": "Open notifications panel", "notifications_cat_all": "All", - "notifications_cat_friends": "Friend requests", + "notifications_cat_friends": "Contact requests", "notifications_cat_pads": "Shared with me", "notifications_cat_archived": "History", "notifications_dismissAll": "Dismiss all", @@ -1153,7 +1153,7 @@ "features_emailRequired": "Email address required", "owner_removeText": "Remove an existing owner", "owner_removePendingText": "Cancel a pending offer", - "owner_addText": "Offer co-ownership to a friend", + "owner_addText": "Offer co-ownership to a contact", "owner_unknownUser": "Unknown user", "owner_removeButton": "Remove selected owners", "owner_removePendingButton": "Cancel selected offers", @@ -1169,9 +1169,9 @@ "owner_removed": "{0} has removed your ownership of {1}", "owner_removedPending": "{0} has canceled your ownership offer for {1}", "share_linkTeam": "Add to team drive", - "team_pickFriends": "Choose which friends to invite to this team", + "team_pickFriends": "Choose which contacts to invite to this team", "team_inviteModalButton": "Invite", - "team_noFriend": "You haven't connected with any friends on CryptPad yet.", + "team_noFriend": "You haven't connected with any contacts on CryptPad yet.", "team_pcsSelectLabel": "Store in", "team_pcsSelectHelp": "Creating an owned pad in your team's drive will give ownership to the team.", "team_invitedToTeam": "{0} has invited you to join their team: {1}", @@ -1193,7 +1193,7 @@ "team_rosterPromote": "Promote", "team_rosterDemote": "Demote", "team_rosterKick": "Kick from the team", - "team_inviteButton": "Invite friends", + "team_inviteButton": "Invite contacts", "team_leaveButton": "Leave this team", "team_leaveConfirm": "If you leave this team you will lose access to its CryptDrive, chat history, and other contents. Are you sure?", "team_owner": "Owners", From cd586b626d640280e00aac5658e66a69840edc61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Mon, 27 Jan 2020 14:06:57 +0000 Subject: [PATCH 17/48] unify keys that say 'X is/is not in your contacts' --- www/common/common-ui-elements.js | 4 ++-- www/common/translations/messages.json | 5 ++--- www/profile/inner.js | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index eb1169994..b5e5aa90a 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -4602,10 +4602,10 @@ define([ var f = priv.friends[curve]; $verified.append(h('span.fa.fa-certificate')); var $avatar = $(h('span.cp-avatar')).appendTo($verified); - $verified.append(h('p', Messages._getKey('requestEdit_fromFriend', [f.displayName]))); + $verified.append(h('p', Messages._getKey('isContact', [f.displayName]))); common.displayAvatar($avatar, f.avatar, f.displayName); } else { - $verified.append(Messages._getKey('requestEdit_fromStranger', [name])); + $verified.append(Messages._getKey('isNotContact', [name])); } return verified; }; diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json index 15dbe5184..880edc12f 100644 --- a/www/common/translations/messages.json +++ b/www/common/translations/messages.json @@ -1086,7 +1086,8 @@ "profile_addLink": "Add a link to your website", "profile_info": "Other users can find your profile through your avatar in document user lists.", "profile_friendRequestSent": "Contact request pending...", - "profile_friend": "{0} is your friend", + "isContact": "{0} is in your contacts", + "isNotContact": "{0} is not in your contacts", "notification_padShared": "{0} has shared a pad with you: {1}", "notification_fileShared": "{0} has shared a file with you: {1}", "notification_folderShared": "{0} has shared a folder with you: {1}", @@ -1138,8 +1139,6 @@ "requestEdit_button": "Request edit rights", "requestEdit_dialog": "Are you sure you'd like to ask the owner of this pad for the ability to edit?", "requestEdit_confirm": "{1} has asked for the ability to edit the pad {0}. Would you like to grant them access?", - "requestEdit_fromFriend": "You are friends with {0}", - "requestEdit_fromStranger": "You are not friends with {0}", "requestEdit_viewPad": "Open the pad in a new tab", "later": "Decide later", "requestEdit_request": "{1} wants to edit the pad {0}", diff --git a/www/profile/inner.js b/www/profile/inner.js index d5243036c..0f4a17746 100644 --- a/www/profile/inner.js +++ b/www/profile/inner.js @@ -201,7 +201,7 @@ define([ // Add friend message APP.$friend.append(h('p.cp-app-profile-friend', [ h('i.fa.fa-address-book'), - Messages._getKey('profile_friend', [name]) + Messages._getKey('isContact', [name]) ])); if (!friends[data.curvePublic].notifications) { return; } // Add unfriend button From 7a02b074b734d7494ffe3ed3150a5c2c3290d764 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 27 Jan 2020 15:45:57 +0100 Subject: [PATCH 18/48] Hidden hash for files --- www/common/common-hash.js | 76 +++++++++++++++++++------------ 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, 67 insertions(+), 36 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); + 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(); - 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 - }; - }; 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 19/48] 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 20/48] 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 21/48] 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 22/48] 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 23/48] 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 24/48] 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 25/48] 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) { + $('