diff --git a/CHANGELOG.md b/CHANGELOG.md index df249c258..1293b578d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.6.6 + * FIX: line numbers are aligned with text again (broken in 1.6.4) + * FIX: text entered between connection loss and reconnection was not saved + * FIX: diagnostic call failed when etherpad was exposed in a subdirectory + # 1.6.5 * SECURITY: Escape data when listing available plugins * FIX: Fix typo in apicalls.js which prevented importing isValidJSONPName @@ -6,9 +11,9 @@ * FIX: unbreak Safari iOS line wrapping # 1.6.4 - * SECURITY: exploitable /admin access - CVE-2018-9845 - * SECURITY: DoS with pad exports - CVE-2018-9327 - * SECURITY: Remote Code Execution - CVE-2018-9326 + * SECURITY: Access Control bypass on /admin - CVE-2018-9845 + * SECURITY: Remote Code Execution through pad export - CVE-2018-9327 + * SECURITY: Remote Code Execution through JSONP handling - CVE-2018-9326 * SECURITY: Pad data leak - CVE-2018-9325 * Fix: Admin redirect URL * Fix: Various script Fixes diff --git a/bin/createRelease.sh b/bin/createRelease.sh index 5afced8f2..0439026bd 100755 --- a/bin/createRelease.sh +++ b/bin/createRelease.sh @@ -65,9 +65,21 @@ function check_api_token { function modify_files { # Add changelog text to first line of CHANGELOG.md - sed -i "1s/^/${changelogText}\n/" CHANGELOG.md + + msg="" + # source: https://unix.stackexchange.com/questions/9784/how-can-i-read-line-by-line-from-a-variable-in-bash#9789 + while IFS= read -r line + do + # replace newlines with literal "\n" for using with sed + msg+="$line\n" + done < <(printf '%s\n' "${changelogText}") + + sed -i "1s/^/${msg}\n/" CHANGELOG.md + [[ $? != 0 ]] && echo "Aborting: Error modifying CHANGELOG.md" && exit 1 + # Replace version number of etherpad in package.json sed -i -r "s/(\"version\"[ ]*: \").*(\")/\1$VERSION\2/" src/package.json + [[ $? != 0 ]] && echo "Aborting: Error modifying package.json" && exit 1 } function create_release_branch { diff --git a/src/locales/pms.json b/src/locales/pms.json index 80a857e39..cdc80bea3 100644 --- a/src/locales/pms.json +++ b/src/locales/pms.json @@ -44,5 +44,86 @@ "pad.importExport.import": "Carié n'archivi o document ëd test", "pad.importExport.importSuccessful": "Bele fàit!", "pad.importExport.export": "Esporté ël feuj atual coma:", - "pad.importExport.exportetherpad": "Etherpad" + "pad.importExport.exportetherpad": "Etherpad", + "pad.importExport.exporthtml": "HTML", + "pad.importExport.exportplain": "Mach test", + "pad.importExport.exportword": "Microsoft Word", + "pad.importExport.exportpdf": "PDF", + "pad.importExport.exportopen": "ODF (Open Document Format)", + "pad.importExport.abiword.innerHTML": "A peul mach amporté dij formà ëd test sempi o HTML. Për dle fonsionalità d'amportassion pi avansà, ch'a anstala AbiWord.", + "pad.modals.connected": "Colegà.", + "pad.modals.reconnecting": "Neuva conession a sò feuj...", + "pad.modals.forcereconnect": "Forsé la neuva conession", + "pad.modals.reconnecttimer": "Tentativ ëd neuva conession", + "pad.modals.cancel": "Anulé", + "pad.modals.userdup": "Duvertà an n'àutra fnestra", + "pad.modals.userdup.explanation": "Ës feuj a smija esse duvert an vàire fnestre ansima a st'ordinator.", + "pad.modals.userdup.advice": "Coleghesse torna për dovré costa fnestra.", + "pad.modals.unauth": "Nen autorisà", + "pad.modals.unauth.explanation": "Ij sò përmess a son cangià antramentre ch'a vëdìa costa pàgina. Ch'a sërca ëd coleghesse torna.", + "pad.modals.looping.explanation": "A-i é dij problema ëd comunicassion con ël servent ëd sincronisassion.", + "pad.modals.looping.cause": "Peul desse che chiel a l'é colegasse con un para-feu o un mandatari incompatìbil.", + "pad.modals.initsocketfail": "Ël servent a l'é introvàbil.", + "pad.modals.initsocketfail.explanation": "Impossìbil coleghesse al servent ëd sincronisassion.", + "pad.modals.initsocketfail.cause": "A l'é probàbil che sòn a sia dovù a sò navigador o a soa conession an sl'aragnà.", + "pad.modals.slowcommit.explanation": "Ël servent a rëspond nen.", + "pad.modals.slowcommit.cause": "Sòn a podrìa esse dovù a dij problema ëd conession a l'aragnà.", + "pad.modals.badChangeset.explanation": "Na modìfica ch'a l'ha fàit a l'é stàita cassificà tanme ilegal dal servent ëd sincronisassion.", + "pad.modals.badChangeset.cause": "Sòn a podrìa esse dovù a na bruta configurassion dël servent o a chèich àutr comportament nen ëspetà. Për piasì, ch'a contata l'aministrator dël servissi, s'a pensa ch'a sia n'eror. Ch'a preuva a rintré torna ant ël sistema për andé anans a modifiché.", + "pad.modals.corruptPad.explanation": "Ël feuj al qual a sërca d'acede a l'é corompù.", + "pad.modals.corruptPad.cause": "Sòn a podrìa esse dovù a na configurassion ësbalià dël servent o a chèich àutr comportament nen ëspetà. Për piasì, ch'a contata l'aministrator dël servissi.", + "pad.modals.deleted": "Dëscancelà.", + "pad.modals.deleted.explanation": "Ës feuj a l'é stàit eliminà.", + "pad.modals.disconnected": "A l'é stàit dëscolegà", + "pad.modals.disconnected.explanation": "La conession al servent a l'é perdusse", + "pad.modals.disconnected.cause": "Ël servent a podrìa esse indisponìbil. Për piasì, ch'a anforma l'aministrator dël servissi si ël problema a persist.", + "pad.share": "Partagé 's feuj", + "pad.share.readonly": "Mach letura", + "pad.share.link": "Liura", + "pad.share.emebdcode": "Ancorporé na liura", + "pad.chat": "Ciaciarada", + "pad.chat.title": "Duverté la ciaciarada për cost feuj.", + "pad.chat.loadmessages": "Carié pi 'd mëssagi", + "timeslider.pageTitle": "Stòria dinàmica ëd {{appTitle}}", + "timeslider.toolbar.returnbutton": "Torné al feuj", + "timeslider.toolbar.authors": "Autor:", + "timeslider.toolbar.authorsList": "Gnun autor", + "timeslider.toolbar.exportlink.title": "Esporté", + "timeslider.exportCurrent": "Esporté la version corenta tanme:", + "timeslider.version": "Version {{version}}", + "timeslider.saved": "Argistrà ai {{day}} {{month}} {{year}}", + "timeslider.playPause": "Letura / Pàusa dij contnù dël feuj", + "timeslider.backRevision": "Andé andaré ëd na revision ant ës feuj", + "timeslider.forwardRevision": "Andé anans ëd na revision ant ëd feuj", + "timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}", + "timeslider.month.january": "Gené", + "timeslider.month.february": "Fërvé", + "timeslider.month.march": "Mars", + "timeslider.month.april": "Avril", + "timeslider.month.may": "Maj", + "timeslider.month.june": "Giugn", + "timeslider.month.july": "Luj", + "timeslider.month.august": "Ost", + "timeslider.month.september": "Stèmber", + "timeslider.month.october": "Otóber", + "timeslider.month.november": "Novèmber", + "timeslider.month.december": "Dzèmber", + "timeslider.unnamedauthors": "{{num}} {[plural(num) one: autor anònim, other: autor anònim ]}", + "pad.savedrevs.marked": "Sa revision a l'é adess marcà tanme revision argistrà", + "pad.savedrevs.timeslider": "A peul vëdde le revision argistrà an visitand la stòria", + "pad.userlist.entername": "Ch'a buta sò nòm", + "pad.userlist.unnamed": "anònim", + "pad.userlist.guest": "Anvità", + "pad.userlist.deny": "Arfudé", + "pad.userlist.approve": "Aprové", + "pad.editbar.clearcolors": "Dëscancelé ij color ëd paternità dj'autor an tut ël document?", + "pad.impexp.importbutton": "Amporté adess", + "pad.impexp.importing": "An camin ch'as ampòrta...", + "pad.impexp.confirmimport": "Amportand n'archivi as dëscancelërà ël test corent dël feuj. É-lo sigur ëd vorèj felo?", + "pad.impexp.convertFailed": "I l'oma nen podù amporté s'archivi. Për piasì, ch'a deuvra n'àutr formà ëd document o ch'a còpia e ancòla a man", + "pad.impexp.padHasData": "I l'oma nen podù amporté s'archivi përché 's feuj a l'ha già avù dle modìfiche; për piasì, ch'a ampòrta un feuj neuv", + "pad.impexp.uploadFailed": "Ël cariament a l'ha falì, për piasì ch'a preuva torna", + "pad.impexp.importfailed": "Amportassion falìa", + "pad.impexp.copypaste": "Për piasì, ch'a còpia e ancòla", + "pad.impexp.exportdisabled": "L'esportassion an formà {{type}} a l'é disativà. Për piasì, ch'a contata sò aministrator ëd sistema për ij detaj." } diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 060bca7b9..b575125a2 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -1153,6 +1153,101 @@ function handleClientReady(client, message) client.join(padIds.padId); //Save the revision in sessioninfos, we take the revision from the info the client send to us sessioninfos[client.id].rev = message.client_rev; + + //During the client reconnect, client might miss some revisions from other clients. By using client revision, + //this below code sends all the revisions missed during the client reconnect + var revisionsNeeded = []; + var changesets = {}; + + var startNum = message.client_rev + 1; + var endNum = pad.getHeadRevisionNumber() + 1; + + async.series([ + //push all the revision numbers needed into revisionsNeeded array + function(callback) + { + var headNum = pad.getHeadRevisionNumber(); + if (endNum > headNum+1) + endNum = headNum+1; + if (startNum < 0) + startNum = 0; + + for(var r=startNum;rdiv{ - padding: 1px; -} - body.doesWrap { /* white-space: pre-wrap; */ diff --git a/src/static/js/collab_client.js b/src/static/js/collab_client.js index fd0d9d446..4bcc6ad2b 100644 --- a/src/static/js/collab_client.js +++ b/src/static/js/collab_client.js @@ -60,6 +60,8 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) var debugMessages = []; var msgQueue = []; + var isPendingRevision = false; + tellAceAboutHistoricalAuthors(serverVars.historicalAuthorData); tellAceActiveAuthorInfo(initialUserInfo); @@ -178,23 +180,36 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) editor.applyChangesToBase(changeset, author, apool); } } + if (isPendingRevision) { + setIsPendingRevision(false); + } } var sentMessage = false; - var userChangesData = editor.prepareUserChangeset(); - if (userChangesData.changeset) + // Check if there are any pending revisions to be received from server. + // Allow only if there are no pending revisions to be received from server + if (!isPendingRevision) { - lastCommitTime = t; - state = "COMMITTING"; - stateMessage = { - type: "USER_CHANGES", - baseRev: rev, - changeset: userChangesData.changeset, - apool: userChangesData.apool - }; - sendMessage(stateMessage); - sentMessage = true; - callbacks.onInternalAction("commitPerformed"); + var userChangesData = editor.prepareUserChangeset(); + if (userChangesData.changeset) + { + lastCommitTime = t; + state = "COMMITTING"; + stateMessage = { + type: "USER_CHANGES", + baseRev: rev, + changeset: userChangesData.changeset, + apool: userChangesData.apool + }; + sendMessage(stateMessage); + sentMessage = true; + callbacks.onInternalAction("commitPerformed"); + } + } + else + { + // run again in a few seconds, to check if there was a reconnection attempt + setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000); } if (sentMessage) @@ -330,6 +345,69 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) }); handleUserChanges(); } + else if (msg.type == 'CLIENT_RECONNECT') + { + // Server sends a CLIENT_RECONNECT message when there is a client reconnect. Server also returns + // all pending revisions along with this CLIENT_RECONNECT message + if (msg.noChanges) + { + // If no revisions are pending, just make everything normal + setIsPendingRevision(false); + return; + } + + var headRev = msg.headRev; + var newRev = msg.newRev; + var changeset = msg.changeset; + var author = (msg.author || ''); + var apool = msg.apool; + + if (msgQueue.length > 0) + { + if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1)) + { + window.console.warn("bad message revision on CLIENT_RECONNECT: " + newRev + " not " + (msgQueue[msgQueue.length - 1][0] + 1)); + // setChannelState("DISCONNECTED", "badmessage_acceptcommit"); + return; + } + msg.type = "NEW_CHANGES"; + msgQueue.push(msg); + return; + } + + if (newRev != (rev + 1)) + { + window.console.warn("bad message revision on CLIENT_RECONNECT: " + newRev + " not " + (rev + 1)); + // setChannelState("DISCONNECTED", "badmessage_acceptcommit"); + return; + } + + rev = newRev; + if (author == pad.getUserId()) + { + editor.applyPreparedChangesetToBase(); + setStateIdle(); + callCatchingErrors("onInternalAction", function() + { + callbacks.onInternalAction("commitAcceptedByServer"); + }); + callCatchingErrors("onConnectionTrouble", function() + { + callbacks.onConnectionTrouble("OK"); + }); + handleUserChanges(); + } + else + { + editor.applyChangesToBase(changeset, author, apool); + } + + if (newRev == headRev) + { + // Once we have applied all pending revisions, make everything normal + setIsPendingRevision(false); + } + } else if (msg.type == "NO_COMMIT_PENDING") { if (state == "COMMITTING") @@ -591,6 +669,11 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) schedulePerhapsCallIdleFuncs(); } + function setIsPendingRevision(value) + { + isPendingRevision = value; + } + function callWhenNotCommitting(func) { idleFuncs.push(func); @@ -656,7 +739,9 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) getMissedChanges: getMissedChanges, callWhenNotCommitting: callWhenNotCommitting, addHistoricalAuthors: tellAceAboutHistoricalAuthors, - setChannelState: setChannelState + setChannelState: setChannelState, + setStateIdle: setStateIdle, + setIsPendingRevision: setIsPendingRevision }; $(document).ready(setUpSocket); diff --git a/src/static/js/pad.js b/src/static/js/pad.js index 8fcec23f1..de613910d 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -206,10 +206,12 @@ function handshake() socket.on('reconnect', function () { pad.collabClient.setChannelState("CONNECTED"); - pad.sendClientReady(true); + pad.sendClientReady(receivedClientVars); }); socket.on('reconnecting', function() { + pad.collabClient.setStateIdle(); + pad.collabClient.setIsPendingRevision(true); pad.collabClient.setChannelState("RECONNECTING"); }); @@ -217,6 +219,11 @@ function handshake() pad.collabClient.setChannelState("DISCONNECTED", "reconnect_timeout"); }); + socket.on('error', function(error) { + pad.collabClient.setStateIdle(); + pad.collabClient.setIsPendingRevision(true); + }); + var initalized = false; socket.on('message', function(obj) @@ -831,7 +838,7 @@ var pad = { $.ajax( { type: 'post', - url: '/ep/pad/connection-diagnostic-info', + url: 'ep/pad/connection-diagnostic-info', data: { diagnosticInfo: JSON.stringify(pad.diagnosticInfo) },