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)
},