From 9497ee734f415daa266e015ea4b5deb45033aa38 Mon Sep 17 00:00:00 2001 From: muxator Date: Fri, 8 Feb 2019 23:20:57 +0100 Subject: [PATCH] prepare to async: trivial reformatting This change is only cosmetic. Its aim is do make it easier to understand the async changes that are going to be merged later on. It was extracted from the original work from Ray Bellis. To verify that nothing has changed, you can run the following command on each file touched by this commit: npm install uglify-es diff --unified <(uglify-js --beautify bracketize ) <(uglify-js --beautify bracketize ) This is a complete script that does the same automatically (works from a mercurial clone): ```bash #!/usr/bin/env bash set -eu REVISION= PARENT_REV=$(hg identify --rev "${REVISION}" --template '{p1rev}') FILE_LIST=$(hg status --no-status --change ${REVISION}) UGLIFYJS="node_modules/uglify-es/bin/uglifyjs" for FILE_NAME in ${FILE_LIST[@]}; do echo "Checking ${FILE_NAME}" diff --unified \ <("${UGLIFYJS}" --beautify bracketize <(hg cat --rev "${PARENT_REV}" "${FILE_NAME}")) \ <("${UGLIFYJS}" --beautify bracketize <(hg cat --rev "${REVISION}" "${FILE_NAME}")) done ``` --- bin/checkAllPads.js | 207 +++-- bin/checkPad.js | 130 ++- bin/deletePad.js | 50 +- bin/extractPadData.js | 118 ++- src/node/db/API.js | 762 +++++++--------- src/node/db/AuthorManager.js | 163 ++-- src/node/db/DB.js | 27 +- src/node/db/GroupManager.js | 293 +++---- src/node/db/Pad.js | 463 +++++----- src/node/db/PadManager.js | 177 ++-- src/node/db/ReadOnlyManager.js | 49 +- src/node/db/SecurityManager.js | 350 ++++---- src/node/db/SessionManager.js | 248 +++--- src/node/db/SessionStore.js | 42 +- src/node/handler/APIHandler.js | 96 +- src/node/handler/ExportHandler.js | 117 ++- src/node/handler/ImportHandler.js | 152 ++-- src/node/handler/PadMessageHandler.js | 1010 +++++++++++----------- src/node/handler/SocketIORouter.js | 104 ++- src/node/hooks/express/adminplugins.js | 103 ++- src/node/hooks/express/errorhandling.js | 15 +- src/node/hooks/express/importexport.js | 14 +- src/node/hooks/express/padreadonly.js | 39 +- src/node/hooks/express/padurlsanitize.js | 19 +- src/node/hooks/express/tests.js | 51 +- src/node/padaccess.js | 10 +- src/node/server.js | 24 +- src/node/utils/ExportTxt.js | 165 ++-- src/node/utils/ImportEtherpad.js | 43 +- src/node/utils/ImportHtml.js | 26 +- src/node/utils/padDiff.js | 498 ++++++----- src/static/js/pluginfw/installer.js | 77 +- src/static/js/pluginfw/plugins.js | 7 +- 33 files changed, 2706 insertions(+), 2943 deletions(-) diff --git a/bin/checkAllPads.js b/bin/checkAllPads.js index a94c38d23..c4467fa79 100644 --- a/bin/checkAllPads.js +++ b/bin/checkAllPads.js @@ -1,144 +1,133 @@ /* - This is a debug tool. It checks all revisions for data corruption -*/ + * This is a debug tool. It checks all revisions for data corruption + */ -if(process.argv.length != 2) -{ +if (process.argv.length != 2) { console.error("Use: node bin/checkAllPads.js"); process.exit(1); } -//initialize the variables +// initialize the variables var db, settings, padManager; -var npm = require("../src/node_modules/npm"); -var async = require("../src/node_modules/async"); +var npm = require('../src/node_modules/npm'); +var async = require('../src/node_modules/async'); -var Changeset = require("../src/static/js/Changeset"); +var Changeset = require('../src/static/js/Changeset'); async.series([ - //load npm + // load npm function(callback) { npm.load({}, callback); }, - //load modules + + // load modules function(callback) { settings = require('../src/node/utils/Settings'); db = require('../src/node/db/DB'); - //initialize the database + // initialize the database db.init(callback); }, - //load pads - function (callback) - { + + // load pads + function (callback) { padManager = require('../src/node/db/PadManager'); - - padManager.listAllPads(function(err, res) - { + + padManager.listAllPads(function(err, res) { padIds = res.padIDs; callback(err); }); }, - function (callback) - { - async.forEach(padIds, function(padId, callback) - { - padManager.getPad(padId, function(err, pad) { + + function (callback) { + async.forEach(padIds, function(padId, callback) { + padManager.getPad(padId, function(err, pad) { + if (err) { + callback(err); + } + + // check if the pad has a pool + if (pad.pool === undefined ) { + console.error("[" + pad.id + "] Missing attribute pool"); + callback(); + + return; + } + + // create an array with key kevisions + // key revisions always save the full pad atext + var head = pad.getHeadRevisionNumber(); + var keyRevisions = []; + for (var i = 0; i < head; i += 100) { + keyRevisions.push(i); + } + + // run through all key revisions + async.forEachSeries(keyRevisions, function(keyRev, callback) { + // create an array of revisions we need till the next keyRevision or the End + var revisionsNeeded = []; + + for(var i = keyRev; i <= keyRev + 100 && i <= head; i++) { + revisionsNeeded.push(i); + } + + // this array will hold all revision changesets + var revisions = []; + + // run through all needed revisions and get them from the database + async.forEach(revisionsNeeded, function(revNum, callback) { + db.db.get("pad:" + pad.id + ":revs:" + revNum, function(err, revision) { + revisions[revNum] = revision; + callback(err); + }); + }, + + function(err) { if (err) { - callback(err); + callback(err); + return; } - - //check if the pad has a pool - if(pad.pool === undefined ) - { - console.error("[" + pad.id + "] Missing attribute pool"); + + // check if the revision exists + if (revisions[keyRev] == null) { + console.error("[" + pad.id + "] Missing revision " + keyRev); + callback(); + return; + } + + // check if there is a atext in the keyRevisions + if (revisions[keyRev].meta === undefined || revisions[keyRev].meta.atext === undefined) { + console.error("[" + pad.id + "] Missing atext in revision " + keyRev); + callback(); + return; + } + + var apool = pad.pool; + var atext = revisions[keyRev].meta.atext; + + for(var i = keyRev + 1; i <= keyRev + 100 && i <= head; i++) { + try { + // console.log("[" + pad.id + "] check revision " + i); + var cs = revisions[i].changeset; + atext = Changeset.applyToAText(cs, atext, apool); + } catch(e) { + console.error("[" + pad.id + "] Bad changeset at revision " + i + " - " + e.message); callback(); return; + } } - //create an array with key kevisions - //key revisions always save the full pad atext - var head = pad.getHeadRevisionNumber(); - var keyRevisions = []; - for(var i=0;i pad.getHeadRevisionNumber()) - { - callback(new customError("rev is higher than the head revision of the pad","apierror")); + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; + + // the client asked for a special revision + if (rev !== undefined) { + // check if this is a valid revision + if (rev > pad.getHeadRevisionNumber()) { + callback(new customError("rev is higher than the head revision of the pad", "apierror")); return; } - - //get the changeset for this revision - pad.getRevisionChangeset(rev, function(err, changeset) - { - if(ERR(err, callback)) return; - + + // get the changeset for this revision + pad.getRevisionChangeset(rev, function(err, changeset) { + if (ERR(err, callback)) return; + callback(null, changeset); }) return; } - //the client wants the latest changeset, lets return it to him - pad.getRevisionChangeset(pad.getHeadRevisionNumber(), function(err, changeset) - { - if(ERR(err, callback)) return; + // the client wants the latest changeset, lets return it to him + pad.getRevisionChangeset(pad.getHeadRevisionNumber(), function(err, changeset) { + if (ERR(err, callback)) return; callback(null, changeset); }) @@ -191,7 +182,7 @@ exports.getRevisionChangeset = function(padID, rev, callback) } /** -getText(padID, [rev]) returns the text of a pad +getText(padID, [rev]) returns the text of a pad Example returns: @@ -200,69 +191,61 @@ Example returns: */ exports.getText = function(padID, rev, callback) { - //check if rev is a number - if(rev !== undefined && typeof rev != "number") - { - //try to parse the number - if(isNaN(parseInt(rev))) - { + // check if rev is a number + if (rev !== undefined && typeof rev != "number") { + // try to parse the number + if (isNaN(parseInt(rev))) { callback(new customError("rev is not a number", "apierror")); return; } rev = parseInt(rev); } - - //ensure this is not a negativ number - if(rev !== undefined && rev < 0) - { - callback(new customError("rev is a negativ number","apierror")); + + // ensure this is not a negative number + if (rev !== undefined && rev < 0) { + callback(new customError("rev is a negativ number", "apierror")); return; } - - //ensure this is not a float value - if(rev !== undefined && !is_int(rev)) - { - callback(new customError("rev is a float value","apierror")); + + // ensure this is not a float value + if (rev !== undefined && !is_int(rev)) { + callback(new customError("rev is a float value", "apierror")); return; } - - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; - - //the client asked for a special revision - if(rev !== undefined) - { - //check if this is a valid revision - if(rev > pad.getHeadRevisionNumber()) - { - callback(new customError("rev is higher than the head revision of the pad","apierror")); + + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; + + // the client asked for a special revision + if (rev !== undefined) { + // check if this is a valid revision + if (rev > pad.getHeadRevisionNumber()) { + callback(new customError("rev is higher than the head revision of the pad", "apierror")); return; } - - //get the text of this revision - pad.getInternalRevisionAText(rev, function(err, atext) - { - if(ERR(err, callback)) return; - + + // get the text of this revision + pad.getInternalRevisionAText(rev, function(err, atext) { + if (ERR(err, callback)) return; + var data = {text: atext.text}; - + callback(null, data); }) return; } - //the client wants the latest text, lets return it to him + // the client wants the latest text, lets return it to him var padText = exportTxt.getTXTFromAtext(pad, pad.atext); callback(null, {"text": padText}); }); } /** -setText(padID, text) sets the text of a pad +setText(padID, text) sets the text of a pad Example returns: @@ -271,23 +254,21 @@ Example returns: {code: 1, message:"text too long", data: null} */ exports.setText = function(padID, text, callback) -{ - //text is required - if(typeof text != "string") - { - callback(new customError("text is no string","apierror")); +{ + // text is required + if (typeof text != "string") { + callback(new customError("text is no string", "apierror")); return; } - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; - - //set the text + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; + + // set the text pad.setText(text); - - //update the clients on the pad + + // update the clients on the pad padMessageHandler.updatePadClients(pad, callback); }); } @@ -303,29 +284,25 @@ Example returns: */ exports.appendText = function(padID, text, callback) { - //text is required - if(typeof text != "string") - { - callback(new customError("text is no string","apierror")); + // text is required + if (typeof text != "string") { + callback(new customError("text is no string", "apierror")); return; } - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; pad.appendText(text); - //update the clients on the pad + // update the clients on the pad padMessageHandler.updatePadClients(pad, callback); }); }; - - /** -getHTML(padID, [rev]) returns the html of a pad +getHTML(padID, [rev]) returns the html of a pad Example returns: @@ -334,47 +311,39 @@ Example returns: */ exports.getHTML = function(padID, rev, callback) { - if (rev !== undefined && typeof rev != "number") - { - if (isNaN(parseInt(rev))) - { - callback(new customError("rev is not a number","apierror")); + if (rev !== undefined && typeof rev != "number") { + if (isNaN(parseInt(rev))) { + callback(new customError("rev is not a number", "apierror")); return; } rev = parseInt(rev); } - if(rev !== undefined && rev < 0) - { - callback(new customError("rev is a negative number","apierror")); + if (rev !== undefined && rev < 0) { + callback(new customError("rev is a negative number", "apierror")); return; } - if(rev !== undefined && !is_int(rev)) - { - callback(new customError("rev is a float value","apierror")); + if (rev !== undefined && !is_int(rev)) { + callback(new customError("rev is a float value", "apierror")); return; } - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; - - //the client asked for a special revision - if(rev !== undefined) - { - //check if this is a valid revision - if(rev > pad.getHeadRevisionNumber()) - { - callback(new customError("rev is higher than the head revision of the pad","apierror")); + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; + + // the client asked for a special revision + if (rev !== undefined) { + // check if this is a valid revision + if (rev > pad.getHeadRevisionNumber()) { + callback(new customError("rev is higher than the head revision of the pad", "apierror")); return; } - - //get the html of this revision - exportHtml.getPadHTML(pad, rev, function(err, html) - { - if(ERR(err, callback)) return; + + // get the html of this revision + exportHtml.getPadHTML(pad, rev, function(err, html) { + if (ERR(err, callback)) return; html = "" +html; // adds HTML head html += ""; var data = {html: html}; @@ -384,10 +353,9 @@ exports.getHTML = function(padID, rev, callback) return; } - //the client wants the latest text, lets return it to him - exportHtml.getPadHTML(pad, undefined, function (err, html) - { - if(ERR(err, callback)) return; + // the client wants the latest text, lets return it to him + exportHtml.getPadHTML(pad, undefined, function(err, html) { + if (ERR(err, callback)) return; html = "" +html; // adds HTML head html += ""; var data = {html: html}; @@ -406,26 +374,24 @@ Example returns: */ exports.setHTML = function(padID, html, callback) { - //html is required - if(typeof html != "string") - { - callback(new customError("html is no string","apierror")); + // html is required + if (typeof html != "string") { + callback(new customError("html is no string", "apierror")); return; } - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; // add a new changeset with the new html to the pad - importHtml.setPadHTML(pad, cleanText(html), function(e){ - if(e){ - callback(new customError("HTML is malformed","apierror")); + importHtml.setPadHTML(pad, cleanText(html), function(e) { + if (e) { + callback(new customError("HTML is malformed", "apierror")); return; } - //update the clients on the pad + // update the clients on the pad padMessageHandler.updatePadClients(pad, callback); }); }); @@ -449,54 +415,46 @@ Example returns: */ exports.getChatHistory = function(padID, start, end, callback) { - if(start && end) - { - if(start < 0) - { - callback(new customError("start is below zero","apierror")); + if (start && end) { + if (start < 0) { + callback(new customError("start is below zero", "apierror")); return; } - if(end < 0) - { - callback(new customError("end is below zero","apierror")); + if (end < 0) { + callback(new customError("end is below zero", "apierror")); return; } - if(start > end) - { - callback(new customError("start is higher than end","apierror")); + if (start > end) { + callback(new customError("start is higher than end", "apierror")); return; } } - - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; + + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; + var chatHead = pad.chatHead; - + // fall back to getting the whole chat-history if a parameter is missing - if(!start || !end) - { + if (!start || !end) { start = 0; end = pad.chatHead; } - - if(start > chatHead) - { - callback(new customError("start is higher than the current chatHead","apierror")); + + if (start > chatHead) { + callback(new customError("start is higher than the current chatHead", "apierror")); return; } - if(end > chatHead) - { - callback(new customError("end is higher than the current chatHead","apierror")); + if (end > chatHead) { + callback(new customError("end is higher than the current chatHead", "apierror")); return; } - + // the the whole message-log and return it to the client pad.getChatMessages(start, end, - function(err, msgs) - { - if(ERR(err, callback)) return; + function(err, msgs) { + if (ERR(err, callback)) return; callback(null, {messages: msgs}); }); }); @@ -512,16 +470,14 @@ Example returns: */ exports.appendChatMessage = function(padID, text, authorID, time, callback) { - //text is required - if(typeof text != "string") - { - callback(new customError("text is no string","apierror")); + // text is required + if (typeof text != "string") { + callback(new customError("text is no string", "apierror")); return; } - + // if time is not an integer value - if(time === undefined || !is_int(time)) - { + if (time === undefined || !is_int(time)) { // set time to current timestamp time = Date.now(); } @@ -537,7 +493,7 @@ exports.appendChatMessage = function(padID, text, authorID, time, callback) /*****************/ /** -getRevisionsCount(padID) returns the number of revisions of this pad +getRevisionsCount(padID) returns the number of revisions of this pad Example returns: @@ -546,11 +502,10 @@ Example returns: */ exports.getRevisionsCount = function(padID, callback) { - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; - + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; + callback(null, {revisions: pad.getHeadRevisionNumber()}); }); } @@ -565,10 +520,9 @@ Example returns: */ exports.getSavedRevisionsCount = function(padID, callback) { - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; callback(null, {savedRevisions: pad.getSavedRevisionsNumber()}); }); @@ -584,10 +538,9 @@ Example returns: */ exports.listSavedRevisions = function(padID, callback) { - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; callback(null, {savedRevisions: pad.getSavedRevisionsList()}); }); @@ -603,12 +556,10 @@ Example returns: */ exports.saveRevision = function(padID, rev, callback) { - //check if rev is a number - if(rev !== undefined && typeof rev != "number") - { - //try to parse the number - if(isNaN(parseInt(rev))) - { + // check if rev is a number + if (rev !== undefined && typeof rev != "number") { + // try to parse the number + if (isNaN(parseInt(rev))) { callback(new customError("rev is not a number", "apierror")); return; } @@ -616,32 +567,27 @@ exports.saveRevision = function(padID, rev, callback) rev = parseInt(rev); } - //ensure this is not a negativ number - if(rev !== undefined && rev < 0) - { - callback(new customError("rev is a negativ number","apierror")); + // ensure this is not a negative number + if (rev !== undefined && rev < 0) { + callback(new customError("rev is a negativ number", "apierror")); return; } - //ensure this is not a float value - if(rev !== undefined && !is_int(rev)) - { - callback(new customError("rev is a float value","apierror")); + // ensure this is not a float value + if (rev !== undefined && !is_int(rev)) { + callback(new customError("rev is a float value", "apierror")); return; } - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; - //the client asked for a special revision - if(rev !== undefined) - { - //check if this is a valid revision - if(rev > pad.getHeadRevisionNumber()) - { - callback(new customError("rev is higher than the head revision of the pad","apierror")); + // the client asked for a special revision + if (rev !== undefined) { + // check if this is a valid revision + if (rev > pad.getHeadRevisionNumber()) { + callback(new customError("rev is higher than the head revision of the pad", "apierror")); return; } } else { @@ -649,7 +595,7 @@ exports.saveRevision = function(padID, rev, callback) } authorManager.createAuthor('API', function(err, author) { - if(ERR(err, callback)) return; + if (ERR(err, callback)) return; pad.addSavedRevision(rev, author.authorID, 'Saved through API call'); callback(); @@ -667,19 +613,19 @@ Example returns: */ exports.getLastEdited = function(padID, callback) { - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; + pad.getLastEdit(function(err, value) { - if(ERR(err, callback)) return; + if (ERR(err, callback)) return; callback(null, {lastEdited: value}); }); }); } /** -createPad(padName [, text]) creates a new pad in this group +createPad(padName [, text]) creates a new pad in this group Example returns: @@ -687,34 +633,30 @@ Example returns: {code: 1, message:"pad does already exist", data: null} */ exports.createPad = function(padID, text, callback) -{ - //ensure there is no $ in the padID - if(padID) - { - if(padID.indexOf("$") != -1) - { - callback(new customError("createPad can't create group pads","apierror")); +{ + if (padID) { + // ensure there is no $ in the padID + if (padID.indexOf("$") != -1) { + callback(new customError("createPad can't create group pads", "apierror")); return; } - //check for url special characters - if(padID.match(/(\/|\?|&|#)/)) - { - callback(new customError("malformed padID: Remove special characters","apierror")); + // check for url special characters + if (padID.match(/(\/|\?|&|#)/)) { + callback(new customError("malformed padID: Remove special characters", "apierror")); return; } } - //create pad - getPadSafe(padID, false, text, function(err) - { - if(ERR(err, callback)) return; + // create pad + getPadSafe(padID, false, text, function(err) { + if (ERR(err, callback)) return; callback(); }); } /** -deletePad(padID) deletes a pad +deletePad(padID) deletes a pad Example returns: @@ -723,10 +665,9 @@ Example returns: */ exports.deletePad = function(padID, callback) { - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; - + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; + pad.remove(callback); }); } @@ -738,14 +679,12 @@ exports.deletePad = function(padID, callback) {code:0, message:"ok", data:null} {code: 1, message:"padID does not exist", data: null} */ -exports.restoreRevision = function (padID, rev, callback) +exports.restoreRevision = function(padID, rev, callback) { - //check if rev is a number - if (rev !== undefined && typeof rev != "number") - { - //try to parse the number - if (isNaN(parseInt(rev))) - { + // check if rev is a number + if (rev !== undefined && typeof rev != "number") { + // try to parse the number + if (isNaN(parseInt(rev))) { callback(new customError("rev is not a number", "apierror")); return; } @@ -753,51 +692,43 @@ exports.restoreRevision = function (padID, rev, callback) rev = parseInt(rev); } - //ensure this is not a negativ number - if (rev !== undefined && rev < 0) - { + // ensure this is not a negative number + if (rev !== undefined && rev < 0) { callback(new customError("rev is a negativ number", "apierror")); return; } - //ensure this is not a float value - if (rev !== undefined && !is_int(rev)) - { + // ensure this is not a float value + if (rev !== undefined && !is_int(rev)) { callback(new customError("rev is a float value", "apierror")); return; } - //get the pad - getPadSafe(padID, true, function (err, pad) - { + // get the pad + getPadSafe(padID, true, function(err, pad) { if (ERR(err, callback)) return; - //check if this is a valid revision - if (rev > pad.getHeadRevisionNumber()) - { + // check if this is a valid revision + if (rev > pad.getHeadRevisionNumber()) { callback(new customError("rev is higher than the head revision of the pad", "apierror")); return; } - pad.getInternalRevisionAText(rev, function (err, atext) - { + pad.getInternalRevisionAText(rev, function(err, atext) { if (ERR(err, callback)) return; var oldText = pad.text(); atext.text += "\n"; - function eachAttribRun(attribs, func) - { + function eachAttribRun(attribs, func) { var attribsIter = Changeset.opIterator(attribs); var textIndex = 0; var newTextStart = 0; var newTextEnd = atext.text.length; - while (attribsIter.hasNext()) - { + while (attribsIter.hasNext()) { var op = attribsIter.next(); var nextIndex = textIndex + op.chars; - if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) - { + if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) { func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs); } textIndex = nextIndex; @@ -808,29 +739,25 @@ exports.restoreRevision = function (padID, rev, callback) var builder = Changeset.builder(oldText.length); // assemble each line into the builder - eachAttribRun(atext.attribs, function (start, end, attribs) - { + eachAttribRun(atext.attribs, function(start, end, attribs) { builder.insert(atext.text.substring(start, end), attribs); }); var lastNewlinePos = oldText.lastIndexOf('\n'); - if (lastNewlinePos < 0) - { + if (lastNewlinePos < 0) { builder.remove(oldText.length - 1, 0); - } else - { + } else { builder.remove(lastNewlinePos, oldText.match(/\n/g).length - 1); builder.remove(oldText.length - lastNewlinePos - 1, 0); } var changeset = builder.toString(); - //append the changeset + // append the changeset pad.appendRevision(changeset); - // - padMessageHandler.updatePadClients(pad, function () - { - }); + + // update the clients on the pad + padMessageHandler.updatePadClients(pad, function() {}); callback(null, null); }); @@ -838,7 +765,7 @@ exports.restoreRevision = function (padID, rev, callback) }; /** -copyPad(sourceID, destinationID[, force=false]) copies a pad. If force is true, +copyPad(sourceID, destinationID[, force=false]) copies a pad. If force is true, the destination will be overwritten if it exists. Example returns: @@ -848,16 +775,15 @@ Example returns: */ exports.copyPad = function(sourceID, destinationID, force, callback) { - getPadSafe(sourceID, true, function(err, pad) - { - if(ERR(err, callback)) return; - + getPadSafe(sourceID, true, function(err, pad) { + if (ERR(err, callback)) return; + pad.copy(destinationID, force, callback); }); } /** -movePad(sourceID, destinationID[, force=false]) moves a pad. If force is true, +movePad(sourceID, destinationID[, force=false]) moves a pad. If force is true, the destination will be overwritten if it exists. Example returns: @@ -867,18 +793,17 @@ Example returns: */ exports.movePad = function(sourceID, destinationID, force, callback) { - getPadSafe(sourceID, true, function(err, pad) - { - if(ERR(err, callback)) return; - + getPadSafe(sourceID, true, function(err, pad) { + if (ERR(err, callback)) return; + pad.copy(destinationID, force, function(err) { - if(ERR(err, callback)) return; + if (ERR(err, callback)) return; pad.remove(callback); }); }); } /** -getReadOnlyLink(padID) returns the read only link of a pad +getReadOnlyLink(padID) returns the read only link of a pad Example returns: @@ -887,15 +812,13 @@ Example returns: */ exports.getReadOnlyID = function(padID, callback) { - //we don't need the pad object, but this function does all the security stuff for us - getPadSafe(padID, true, function(err) - { - if(ERR(err, callback)) return; - - //get the readonlyId - readOnlyManager.getReadOnlyId(padID, function(err, readOnlyId) - { - if(ERR(err, callback)) return; + // we don't need the pad object, but this function does all the security stuff for us + getPadSafe(padID, true, function(err) { + if (ERR(err, callback)) return; + + // get the readonlyId + readOnlyManager.getReadOnlyId(padID, function(err, readOnlyId) { + if (ERR(err, callback)) return; callback(null, {readOnlyID: readOnlyId}); }); }); @@ -911,14 +834,12 @@ Example returns: */ exports.getPadID = function(roID, callback) { - //get the PadId - readOnlyManager.getPadId(roID, function(err, retrievedPadID) - { - if(ERR(err, callback)) return; + // get the PadId + readOnlyManager.getPadId(roID, function(err, retrievedPadID) { + if (ERR(err, callback)) return; - if(retrievedPadID == null) - { - callback(new customError("padID does not exist","apierror")); + if (retrievedPadID == null) { + callback(new customError("padID does not exist", "apierror")); return; } @@ -927,7 +848,7 @@ exports.getPadID = function(roID, callback) } /** -setPublicStatus(padID, publicStatus) sets a boolean for the public status of a pad +setPublicStatus(padID, publicStatus) sets a boolean for the public status of a pad Example returns: @@ -936,31 +857,29 @@ Example returns: */ exports.setPublicStatus = function(padID, publicStatus, callback) { - //ensure this is a group pad - if(padID && padID.indexOf("$") == -1) - { - callback(new customError("You can only get/set the publicStatus of pads that belong to a group","apierror")); + // ensure this is a group pad + if (padID && padID.indexOf("$") == -1) { + callback(new customError("You can only get/set the publicStatus of pads that belong to a group", "apierror")); return; } - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; - - //convert string to boolean - if(typeof publicStatus == "string") + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; + + // convert string to boolean + if (typeof publicStatus == "string") publicStatus = publicStatus == "true" ? true : false; - - //set the password + + // set the password pad.setPublicStatus(publicStatus); - + callback(); }); } /** -getPublicStatus(padID) return true of false +getPublicStatus(padID) return true of false Example returns: @@ -969,24 +888,22 @@ Example returns: */ exports.getPublicStatus = function(padID, callback) { - //ensure this is a group pad - if(padID && padID.indexOf("$") == -1) - { - callback(new customError("You can only get/set the publicStatus of pads that belong to a group","apierror")); + // ensure this is a group pad + if (padID && padID.indexOf("$") == -1) { + callback(new customError("You can only get/set the publicStatus of pads that belong to a group", "apierror")); return; } - - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; - + + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; + callback(null, {publicStatus: pad.getPublicStatus()}); }); } /** -setPassword(padID, password) returns ok or a error message +setPassword(padID, password) returns ok or a error message Example returns: @@ -995,27 +912,25 @@ Example returns: */ exports.setPassword = function(padID, password, callback) { - //ensure this is a group pad - if(padID && padID.indexOf("$") == -1) - { - callback(new customError("You can only get/set the password of pads that belong to a group","apierror")); + // ensure this is a group pad + if (padID && padID.indexOf("$") == -1) { + callback(new customError("You can only get/set the password of pads that belong to a group", "apierror")); return; } - - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; - - //set the password + + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; + + // set the password pad.setPassword(password == "" ? null : password); - + callback(); }); } /** -isPasswordProtected(padID) returns true or false +isPasswordProtected(padID) returns true or false Example returns: @@ -1024,24 +939,22 @@ Example returns: */ exports.isPasswordProtected = function(padID, callback) { - //ensure this is a group pad - if(padID && padID.indexOf("$") == -1) - { - callback(new customError("You can only get/set the password of pads that belong to a group","apierror")); + // ensure this is a group pad + if (padID && padID.indexOf("$") == -1) { + callback(new customError("You can only get/set the password of pads that belong to a group", "apierror")); return; } - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; - + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; + callback(null, {isPasswordProtected: pad.isPasswordProtected()}); }); } /** -listAuthorsOfPad(padID) returns an array of authors who contributed to this pad +listAuthorsOfPad(padID) returns an array of authors who contributed to this pad Example returns: @@ -1050,11 +963,10 @@ Example returns: */ exports.listAuthorsOfPad = function(padID, callback) { - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; - + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; + callback(null, {authorIDs: pad.getAllAuthors()}); }); } @@ -1082,8 +994,8 @@ Example returns: {code: 1, message:"padID does not exist"} */ -exports.sendClientsMessage = function (padID, msg, callback) { - getPadSafe(padID, true, function (err, pad) { +exports.sendClientsMessage = function(padID, msg, callback) { + getPadSafe(padID, true, function(err, pad) { if (ERR(err, callback)) { return; } @@ -1115,10 +1027,10 @@ Example returns: */ exports.getChatHead = function(padID, callback) { - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(ERR(err, callback)) return; + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (ERR(err, callback)) return; + callback(null, {chatHead: pad.chatHead}); }); } @@ -1131,69 +1043,64 @@ Example returns: {"code":0,"message":"ok","data":{"html":"Welcome to Etherpad!

This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!

Get involved with Etherpad at http://etherpad.org
aw

","authors":["a.HKIv23mEbachFYfH",""]}} {"code":4,"message":"no or wrong API Key","data":null} */ -exports.createDiffHTML = function(padID, startRev, endRev, callback){ - //check if rev is a number - if(startRev !== undefined && typeof startRev != "number") - { - //try to parse the number - if(isNaN(parseInt(startRev))) - { +exports.createDiffHTML = function(padID, startRev, endRev, callback) { + // check if startRev is a number + if (startRev !== undefined && typeof startRev != "number") { + // try to parse the number + if (isNaN(parseInt(startRev))) { callback({stop: "startRev is not a number"}); return; } startRev = parseInt(startRev, 10); } - - //check if rev is a number - if(endRev !== undefined && typeof endRev != "number") - { - //try to parse the number - if(isNaN(parseInt(endRev))) - { + + // check if endRev is a number + if (endRev !== undefined && typeof endRev != "number") { + // try to parse the number + if (isNaN(parseInt(endRev))) { callback({stop: "endRev is not a number"}); return; } endRev = parseInt(endRev, 10); } - - //get the pad - getPadSafe(padID, true, function(err, pad) - { - if(err){ + + // get the pad + getPadSafe(padID, true, function(err, pad) { + if (err) { return callback(err); } - + try { var padDiff = new PadDiff(pad, startRev, endRev); } catch(e) { return callback({stop:e.message}); } var html, authors; - + async.series([ - function(callback){ - padDiff.getHtml(function(err, _html){ - if(err){ + function(callback) { + padDiff.getHtml(function(err, _html) { + if (err) { return callback(err); } - + html = _html; callback(); }); }, - function(callback){ - padDiff.getAuthors(function(err, _authors){ - if(err){ + function(callback) { + padDiff.getAuthors(function(err, _authors) { + if (err) { return callback(err); } - + authors = _authors; callback(); }); } - ], function(err){ + ], function(err) { callback(err, {html: html, authors: authors}) }); }); @@ -1203,53 +1110,44 @@ exports.createDiffHTML = function(padID, startRev, endRev, callback){ /** INTERNAL HELPER FUNCTIONS */ /******************************/ -//checks if a number is an int +// checks if a number is an int function is_int(value) -{ +{ return (parseFloat(value) == parseInt(value)) && !isNaN(value) } -//gets a pad safe +// gets a pad safe function getPadSafe(padID, shouldExist, text, callback) { - if(typeof text == "function") - { + if (typeof text == "function") { callback = text; text = null; } - //check if padID is a string - if(typeof padID != "string") - { - callback(new customError("padID is not a string","apierror")); + // check if padID is a string + if (typeof padID != "string") { + callback(new customError("padID is not a string", "apierror")); return; } - - //check if the padID maches the requirements - if(!padManager.isValidPadId(padID)) - { - callback(new customError("padID did not match requirements","apierror")); + + // check if the padID maches the requirements + if (!padManager.isValidPadId(padID)) { + callback(new customError("padID did not match requirements", "apierror")); return; } - - //check if the pad exists - padManager.doesPadExists(padID, function(err, exists) - { - if(ERR(err, callback)) return; - - //does not exist, but should - if(exists == false && shouldExist == true) - { - callback(new customError("padID does not exist","apierror")); - } - //does exists, but shouldn't - else if(exists == true && shouldExist == false) - { - callback(new customError("padID does already exist","apierror")); - } - //pad exists, let's get it - else - { + + // check if the pad exists + padManager.doesPadExists(padID, function(err, exists) { + if (ERR(err, callback)) return; + + // does not exist, but should + if (exists == false && shouldExist == true) { + callback(new customError("padID does not exist", "apierror")); + } else if (exists == true && shouldExist == false) { + // does exist, but shouldn't + callback(new customError("padID does already exist", "apierror")); + } else { + // pad exists, let's get it padManager.getPad(padID, text, callback); } }); diff --git a/src/node/db/AuthorManager.js b/src/node/db/AuthorManager.js index bcb6d393d..818eaab28 100644 --- a/src/node/db/AuthorManager.js +++ b/src/node/db/AuthorManager.js @@ -18,25 +18,33 @@ * limitations under the License. */ - var ERR = require("async-stacktrace"); var db = require("./DB").db; var customError = require("../utils/customError"); var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; -exports.getColorPalette = function(){ - return ["#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", "#ffa8a8", "#ffe699", "#cfff9e", "#99ffb3", "#a3ffff", "#99b3ff", "#cc99ff", "#ff99e5", "#e7b1b1", "#e9dcAf", "#cde9af", "#bfedcc", "#b1e7e7", "#c3cdee", "#d2b8ea", "#eec3e6", "#e9cece", "#e7e0ca", "#d3e5c7", "#bce1c5", "#c1e2e2", "#c1c9e2", "#cfc1e2", "#e0bdd9", "#baded3", "#a0f8eb", "#b1e7e0", "#c3c8e4", "#cec5e2", "#b1d5e7", "#cda8f0", "#f0f0a8", "#f2f2a6", "#f5a8eb", "#c5f9a9", "#ececbb", "#e7c4bc", "#daf0b2", "#b0a0fd", "#bce2e7", "#cce2bb", "#ec9afe", "#edabbd", "#aeaeea", "#c4e7b1", "#d722bb", "#f3a5e7", "#ffa8a8", "#d8c0c5", "#eaaedd", "#adc6eb", "#bedad1", "#dee9af", "#e9afc2", "#f8d2a0", "#b3b3e6"]; +exports.getColorPalette = function() { + return [ + "#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", + "#ffa8a8", "#ffe699", "#cfff9e", "#99ffb3", "#a3ffff", "#99b3ff", "#cc99ff", "#ff99e5", + "#e7b1b1", "#e9dcAf", "#cde9af", "#bfedcc", "#b1e7e7", "#c3cdee", "#d2b8ea", "#eec3e6", + "#e9cece", "#e7e0ca", "#d3e5c7", "#bce1c5", "#c1e2e2", "#c1c9e2", "#cfc1e2", "#e0bdd9", + "#baded3", "#a0f8eb", "#b1e7e0", "#c3c8e4", "#cec5e2", "#b1d5e7", "#cda8f0", "#f0f0a8", + "#f2f2a6", "#f5a8eb", "#c5f9a9", "#ececbb", "#e7c4bc", "#daf0b2", "#b0a0fd", "#bce2e7", + "#cce2bb", "#ec9afe", "#edabbd", "#aeaeea", "#c4e7b1", "#d722bb", "#f3a5e7", "#ffa8a8", + "#d8c0c5", "#eaaedd", "#adc6eb", "#bedad1", "#dee9af", "#e9afc2", "#f8d2a0", "#b3b3e6" + ]; }; /** * Checks if the author exists */ -exports.doesAuthorExists = function (authorID, callback) +exports.doesAuthorExists = function(authorID, callback) { - //check if the database entry of this author exists - db.get("globalAuthor:" + authorID, function (err, author) - { - if(ERR(err, callback)) return; + // check if the database entry of this author exists + db.get("globalAuthor:" + authorID, function(err, author) { + if (ERR(err, callback)) return; + callback(null, author != null); }); } @@ -46,12 +54,12 @@ exports.doesAuthorExists = function (authorID, callback) * @param {String} token The token * @param {Function} callback callback (err, author) */ -exports.getAuthor4Token = function (token, callback) +exports.getAuthor4Token = function(token, callback) { - mapAuthorWithDBKey("token2author", token, function(err, author) - { - if(ERR(err, callback)) return; - //return only the sub value authorID + mapAuthorWithDBKey("token2author", token, function(err, author) { + if (ERR(err, callback)) return; + + // return only the sub value authorID callback(null, author ? author.authorID : author); }); } @@ -62,17 +70,17 @@ exports.getAuthor4Token = function (token, callback) * @param {String} name The name of the author (optional) * @param {Function} callback callback (err, author) */ -exports.createAuthorIfNotExistsFor = function (authorMapper, name, callback) +exports.createAuthorIfNotExistsFor = function(authorMapper, name, callback) { - mapAuthorWithDBKey("mapper2author", authorMapper, function(err, author) - { - if(ERR(err, callback)) return; + mapAuthorWithDBKey("mapper2author", authorMapper, function(err, author) { + if (ERR(err, callback)) return; - //set the name of this author - if(name) + if (name) { + // set the name of this author exports.setAuthorName(author.authorID, name); + } - //return the authorID + // return the authorID callback(null, author); }); } @@ -86,33 +94,30 @@ exports.createAuthorIfNotExistsFor = function (authorMapper, name, callback) */ function mapAuthorWithDBKey (mapperkey, mapper, callback) { - //try to map to an author - db.get(mapperkey + ":" + mapper, function (err, author) - { - if(ERR(err, callback)) return; + // try to map to an author + db.get(mapperkey + ":" + mapper, function(err, author) { + if (ERR(err, callback)) return; - //there is no author with this mapper, so create one - if(author == null) - { - exports.createAuthor(null, function(err, author) - { - if(ERR(err, callback)) return; + if (author == null) { + // there is no author with this mapper, so create one + exports.createAuthor(null, function(err, author) { + if (ERR(err, callback)) return; - //create the token2author relation + // create the token2author relation db.set(mapperkey + ":" + mapper, author.authorID); - //return the author + // return the author callback(null, author); }); return; } - //there is a author with this mapper - //update the timestamp of this author + // there is an author with this mapper + // update the timestamp of this author db.setSub("globalAuthor:" + author, ["timestamp"], Date.now()); - //return the author + // return the author callback(null, {authorID: author}); }); } @@ -123,13 +128,17 @@ function mapAuthorWithDBKey (mapperkey, mapper, callback) */ exports.createAuthor = function(name, callback) { - //create the new author name + // create the new author name var author = "a." + randomString(16); - //create the globalAuthors db entry - var authorObj = {"colorId" : Math.floor(Math.random()*(exports.getColorPalette().length)), "name": name, "timestamp": Date.now()}; + // create the globalAuthors db entry + var authorObj = { + "colorId": Math.floor(Math.random() * (exports.getColorPalette().length)), + "name": name, + "timestamp": Date.now() + }; - //set the global author db entry + // set the global author db entry db.set("globalAuthor:" + author, authorObj); callback(null, {authorID: author}); @@ -140,19 +149,17 @@ exports.createAuthor = function(name, callback) * @param {String} author The id of the author * @param {Function} callback callback(err, authorObj) */ -exports.getAuthor = function (author, callback) +exports.getAuthor = function(author, callback) { db.get("globalAuthor:" + author, callback); } - - /** * Returns the color Id of the author * @param {String} author The id of the author * @param {Function} callback callback(err, colorId) */ -exports.getAuthorColorId = function (author, callback) +exports.getAuthorColorId = function(author, callback) { db.getSub("globalAuthor:" + author, ["colorId"], callback); } @@ -163,7 +170,7 @@ exports.getAuthorColorId = function (author, callback) * @param {String} colorId The color id of the author * @param {Function} callback (optional) */ -exports.setAuthorColorId = function (author, colorId, callback) +exports.setAuthorColorId = function(author, colorId, callback) { db.setSub("globalAuthor:" + author, ["colorId"], colorId, callback); } @@ -173,7 +180,7 @@ exports.setAuthorColorId = function (author, colorId, callback) * @param {String} author The id of the author * @param {Function} callback callback(err, name) */ -exports.getAuthorName = function (author, callback) +exports.getAuthorName = function(author, callback) { db.getSub("globalAuthor:" + author, ["name"], callback); } @@ -184,7 +191,7 @@ exports.getAuthorName = function (author, callback) * @param {String} name The name of the author * @param {Function} callback (optional) */ -exports.setAuthorName = function (author, name, callback) +exports.setAuthorName = function(author, name, callback) { db.setSub("globalAuthor:" + author, ["name"], name, callback); } @@ -194,33 +201,33 @@ exports.setAuthorName = function (author, name, callback) * @param {String} author The id of the author * @param {Function} callback (optional) */ -exports.listPadsOfAuthor = function (authorID, callback) +exports.listPadsOfAuthor = function(authorID, callback) { /* There are two other places where this array is manipulated: * (1) When the author is added to a pad, the author object is also updated * (2) When a pad is deleted, each author of that pad is also updated */ - //get the globalAuthor - db.get("globalAuthor:" + authorID, function(err, author) - { - if(ERR(err, callback)) return; - //author does not exists - if(author == null) - { - callback(new customError("authorID does not exist","apierror")) + // get the globalAuthor + db.get("globalAuthor:" + authorID, function(err, author) { + if (ERR(err, callback)) return; + + if (author == null) { + // author does not exist + callback(new customError("authorID does not exist", "apierror")); + return; } - //everything is fine, return the pad IDs + // everything is fine, return the pad IDs var pads = []; - if(author.padIDs != null) - { - for (var padId in author.padIDs) - { + + if (author.padIDs != null) { + for (var padId in author.padIDs) { pads.push(padId); } } + callback(null, {padIDs: pads}); }); } @@ -230,24 +237,22 @@ exports.listPadsOfAuthor = function (authorID, callback) * @param {String} author The id of the author * @param {String} padID The id of the pad the author contributes to */ -exports.addPad = function (authorID, padID) +exports.addPad = function(authorID, padID) { - //get the entry - db.get("globalAuthor:" + authorID, function(err, author) - { - if(ERR(err)) return; - if(author == null) return; + // get the entry + db.get("globalAuthor:" + authorID, function(err, author) { + if (ERR(err)) return; + if (author == null) return; - //the entry doesn't exist so far, let's create it - if(author.padIDs == null) - { + if (author.padIDs == null) { + // the entry doesn't exist so far, let's create it author.padIDs = {}; } - //add the entry for this pad - author.padIDs[padID] = 1;// anything, because value is not used + // add the entry for this pad + author.padIDs[padID] = 1; // anything, because value is not used - //save the new element back + // save the new element back db.set("globalAuthor:" + authorID, author); }); } @@ -257,16 +262,14 @@ exports.addPad = function (authorID, padID) * @param {String} author The id of the author * @param {String} padID The id of the pad the author contributes to */ -exports.removePad = function (authorID, padID) +exports.removePad = function(authorID, padID) { - db.get("globalAuthor:" + authorID, function (err, author) - { - if(ERR(err)) return; - if(author == null) return; + db.get("globalAuthor:" + authorID, function(err, author) { + if (ERR(err)) return; + if (author == null) return; - if(author.padIDs != null) - { - //remove pad from author + if (author.padIDs != null) { + // remove pad from author delete author.padIDs[padID]; db.set("globalAuthor:" + authorID, author); } diff --git a/src/node/db/DB.js b/src/node/db/DB.js index 3c65d5cdb..93848b1bd 100644 --- a/src/node/db/DB.js +++ b/src/node/db/DB.js @@ -1,5 +1,5 @@ /** - * The DB Module provides a database initalized with the settings + * The DB Module provides a database initalized with the settings * provided by the settings module */ @@ -23,7 +23,7 @@ var ueberDB = require("ueberdb2"); var settings = require("../utils/Settings"); var log4js = require('log4js'); -//set database settings +// set database settings var db = new ueberDB.database(settings.dbType, settings.dbSettings, null, log4js.getLogger("ueberDB")); /** @@ -33,24 +33,19 @@ exports.db = null; /** * Initalizes the database with the settings provided by the settings module - * @param {Function} callback + * @param {Function} callback */ -exports.init = function(callback) -{ - //initalize the database async - db.init(function(err) - { - //there was an error while initializing the database, output it and stop - if(err) - { +exports.init = function(callback) { + // initalize the database async + db.init(function(err) { + if (err) { + // there was an error while initializing the database, output it and stop console.error("ERROR: Problem while initalizing the database"); console.error(err.stack ? err.stack : err); process.exit(1); - } - //everything ok - else - { - exports.db = db; + } else { + // everything ok + exports.db = db; callback(null); } }); diff --git a/src/node/db/GroupManager.js b/src/node/db/GroupManager.js index 0c9be1221..5f8183ea9 100644 --- a/src/node/db/GroupManager.js +++ b/src/node/db/GroupManager.js @@ -17,7 +17,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - var ERR = require("async-stacktrace"); var customError = require("../utils/customError"); @@ -29,117 +28,112 @@ var sessionManager = require("./SessionManager"); exports.listAllGroups = function(callback) { db.get("groups", function (err, groups) { - if(ERR(err, callback)) return; - - // there are no groups - if(groups == null) { + if (ERR(err, callback)) return; + + if (groups == null) { + // there are no groups callback(null, {groupIDs: []}); + return; } - + var groupIDs = []; - for ( var groupID in groups ) { + for (var groupID in groups) { groupIDs.push(groupID); } callback(null, {groupIDs: groupIDs}); }); } - + exports.deleteGroup = function(groupID, callback) { var group; async.series([ - //ensure group exists - function (callback) - { - //try to get the group entry - db.get("group:" + groupID, function (err, _group) - { - if(ERR(err, callback)) return; - - //group does not exist - if(_group == null) - { - callback(new customError("groupID does not exist","apierror")); + // ensure group exists + function (callback) { + // try to get the group entry + db.get("group:" + groupID, function (err, _group) { + if (ERR(err, callback)) return; + + if (_group == null) { + // group does not exist + callback(new customError("groupID does not exist", "apierror")); return; } - //group exists, everything is fine + // group exists, everything is fine group = _group; callback(); }); }, - //iterate trough all pads of this groups and delete them - function(callback) - { - //collect all padIDs in an array, that allows us to use async.forEach + + // iterate through all pads of this group and delete them + function(callback) { + // collect all padIDs in an array, that allows us to use async.forEach var padIDs = []; - for(var i in group.pads) - { + for(var i in group.pads) { padIDs.push(i); } - - //loop trough all pads and delete them - async.forEach(padIDs, function(padID, callback) - { - padManager.getPad(padID, function(err, pad) - { - if(ERR(err, callback)) return; - + + // loop through all pads and delete them + async.forEach(padIDs, function(padID, callback) { + padManager.getPad(padID, function(err, pad) { + if (ERR(err, callback)) return; + pad.remove(callback); }); }, callback); }, - //iterate trough group2sessions and delete all sessions + + // iterate through group2sessions and delete all sessions function(callback) { - //try to get the group entry - db.get("group2sessions:" + groupID, function (err, group2sessions) - { - if(ERR(err, callback)) return; - - //skip if there is no group2sessions entry - if(group2sessions == null) {callback(); return} - - //collect all sessions in an array, that allows us to use async.forEach + // try to get the group entry + db.get("group2sessions:" + groupID, function (err, group2sessions) { + if (ERR(err, callback)) return; + + // skip if there is no group2sessions entry + if (group2sessions == null) { callback(); return } + + // collect all sessions in an array, that allows us to use async.forEach var sessions = []; - for(var i in group2sessions.sessionsIDs) - { + for (var i in group2sessions.sessionsIDs) { sessions.push(i); } - - //loop trough all sessions and delete them - async.forEach(sessions, function(session, callback) - { + + // loop through all sessions and delete them + async.forEach(sessions, function(session, callback) { sessionManager.deleteSession(session, callback); }, callback); }); }, - //remove group and group2sessions entry - function(callback) - { + + // remove group and group2sessions entry + function(callback) { db.remove("group2sessions:" + groupID); db.remove("group:" + groupID); callback(); }, - //unlist the group - function(callback) - { + + // unlist the group + function(callback) { exports.listAllGroups(function(err, groups) { - if(ERR(err, callback)) return; + if (ERR(err, callback)) return; groups = groups? groups.groupIDs : []; - // it's not listed - if(groups.indexOf(groupID) == -1) { + if (groups.indexOf(groupID) == -1) { + // it's not listed callback(); + return; } + // remove from the list groups.splice(groups.indexOf(groupID), 1); - - // store empty groupe list - if(groups.length == 0) { + + // store empty group list + if (groups.length == 0) { db.set("groups", {}); callback(); return; @@ -150,50 +144,51 @@ exports.deleteGroup = function(groupID, callback) async.forEach(groups, function(group, cb) { newGroups[group] = 1; cb(); - },function() { + }, + function() { db.set("groups", newGroups); callback(); }); }); } - ], function(err) - { - if(ERR(err, callback)) return; + ], + function(err) { + if (ERR(err, callback)) return; callback(); }); } - + exports.doesGroupExist = function(groupID, callback) { - //try to get the group entry - db.get("group:" + groupID, function (err, group) - { - if(ERR(err, callback)) return; + // try to get the group entry + db.get("group:" + groupID, function (err, group) { + if (ERR(err, callback)) return; callback(null, group != null); }); } exports.createGroup = function(callback) { - //search for non existing groupID + // search for non existing groupID var groupID = "g." + randomString(16); - - //create the group + + // create the group db.set("group:" + groupID, {pads: {}}); - - //list the group + + // list the group exports.listAllGroups(function(err, groups) { - if(ERR(err, callback)) return; + if (ERR(err, callback)) return; + groups = groups? groups.groupIDs : []; - groups.push(groupID); - + // regenerate group list var newGroups = {}; async.forEach(groups, function(group, cb) { newGroups[group] = 1; cb(); - },function() { + }, + function() { db.set("groups", newGroups); callback(null, {groupID: groupID}); }); @@ -202,129 +197,121 @@ exports.createGroup = function(callback) exports.createGroupIfNotExistsFor = function(groupMapper, callback) { - //ensure mapper is optional - if(typeof groupMapper != "string") - { - callback(new customError("groupMapper is no string","apierror")); + // ensure mapper is optional + if (typeof groupMapper != "string") { + callback(new customError("groupMapper is no string", "apierror")); return; } - - //try to get a group for this mapper - db.get("mapper2group:"+groupMapper, function(err, groupID) - { + + // try to get a group for this mapper + db.get("mapper2group:" + groupMapper, function(err, groupID) { function createGroupForMapper(cb) { - exports.createGroup(function(err, responseObj) - { - if(ERR(err, cb)) return; - - //create the mapper entry for this group - db.set("mapper2group:"+groupMapper, responseObj.groupID); - + exports.createGroup(function(err, responseObj) { + if (ERR(err, cb)) return; + + // create the mapper entry for this group + db.set("mapper2group:" + groupMapper, responseObj.groupID); + cb(null, responseObj); }); } - if(ERR(err, callback)) return; - - // there is a group for this mapper - if(groupID) { + if (ERR(err, callback)) return; + + if (groupID) { + // there is a group for this mapper exports.doesGroupExist(groupID, function(err, exists) { - if(ERR(err, callback)) return; - if(exists) return callback(null, {groupID: groupID}); + if (ERR(err, callback)) return; + + if (exists) return callback(null, {groupID: groupID}); // hah, the returned group doesn't exist, let's create one createGroupForMapper(callback) - }) + }); return; } - //there is no group for this mapper, let's create a group + // there is no group for this mapper, let's create a group createGroupForMapper(callback) }); } exports.createGroupPad = function(groupID, padName, text, callback) { - //create the padID + // create the padID var padID = groupID + "$" + padName; async.series([ - //ensure group exists - function (callback) - { - exports.doesGroupExist(groupID, function(err, exists) - { - if(ERR(err, callback)) return; - - //group does not exist - if(exists == false) - { - callback(new customError("groupID does not exist","apierror")); + // ensure group exists + function (callback) { + exports.doesGroupExist(groupID, function(err, exists) { + if (ERR(err, callback)) return; + + if (exists == false) { + // group does not exist + callback(new customError("groupID does not exist", "apierror")); return; } - //group exists, everything is fine + // group exists, everything is fine callback(); }); }, - //ensure pad does not exists - function (callback) - { - padManager.doesPadExists(padID, function(err, exists) - { - if(ERR(err, callback)) return; - - //pad exists already - if(exists == true) - { - callback(new customError("padName does already exist","apierror")); + + // ensure pad doesn't exist already + function (callback) { + padManager.doesPadExists(padID, function(err, exists) { + if (ERR(err, callback)) return; + + if (exists == true) { + // pad exists already + callback(new customError("padName does already exist", "apierror")); return; } - //pad does not exist, everything is fine + // pad does not exist, everything is fine callback(); }); }, - //create the pad - function (callback) - { - padManager.getPad(padID, text, function(err) - { - if(ERR(err, callback)) return; + + // create the pad + function (callback) { + padManager.getPad(padID, text, function(err) { + if (ERR(err, callback)) return; + callback(); }); }, - //create an entry in the group for this pad - function (callback) - { + + // create an entry in the group for this pad + function (callback) { db.setSub("group:" + groupID, ["pads", padID], 1); callback(); } - ], function(err) - { - if(ERR(err, callback)) return; + ], + function(err) { + if (ERR(err, callback)) return; + callback(null, {padID: padID}); }); } exports.listPads = function(groupID, callback) { - exports.doesGroupExist(groupID, function(err, exists) - { - if(ERR(err, callback)) return; - - //group does not exist - if(exists == false) - { - callback(new customError("groupID does not exist","apierror")); + exports.doesGroupExist(groupID, function(err, exists) { + if (ERR(err, callback)) return; + + // ensure the group exists + if (exists == false) { + callback(new customError("groupID does not exist", "apierror")); return; } - //group exists, let's get the pads - db.getSub("group:" + groupID, ["pads"], function(err, result) - { - if(ERR(err, callback)) return; + // group exists, let's get the pads + db.getSub("group:" + groupID, ["pads"], function(err, result) { + if (ERR(err, callback)) return; + var pads = []; for ( var padId in result ) { pads.push(padId); diff --git a/src/node/db/Pad.js b/src/node/db/Pad.js index b74de5228..c5a75a89f 100644 --- a/src/node/db/Pad.js +++ b/src/node/db/Pad.js @@ -19,7 +19,7 @@ var crypto = require("crypto"); var randomString = require("../utils/randomstring"); var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); -//serialization/deserialization attributes +// serialization/deserialization attributes var attributeBlackList = ["id"]; var jsonableList = ["pool"]; @@ -33,7 +33,6 @@ exports.cleanText = function (txt) { var Pad = function Pad(id) { - this.atext = Changeset.makeAText("\n"); this.pool = new AttributePool(); this.head = -1; @@ -60,7 +59,7 @@ Pad.prototype.getSavedRevisionsNumber = function getSavedRevisionsNumber() { Pad.prototype.getSavedRevisionsList = function getSavedRevisionsList() { var savedRev = new Array(); - for(var rev in this.savedRevisions){ + for (var rev in this.savedRevisions) { savedRev.push(this.savedRevisions[rev].revNum); } savedRev.sort(function(a, b) { @@ -74,8 +73,9 @@ Pad.prototype.getPublicStatus = function getPublicStatus() { }; Pad.prototype.appendRevision = function appendRevision(aChangeset, author) { - if(!author) + if (!author) { author = ''; + } var newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool); Changeset.copyAText(newAText, this.atext); @@ -88,21 +88,22 @@ Pad.prototype.appendRevision = function appendRevision(aChangeset, author) { newRevData.meta.author = author; newRevData.meta.timestamp = Date.now(); - //ex. getNumForAuthor - if(author != '') + // ex. getNumForAuthor + if (author != '') { this.pool.putAttrib(['author', author || '']); + } - if(newRev % 100 == 0) - { + if (newRev % 100 == 0) { newRevData.meta.atext = this.atext; } - db.set("pad:"+this.id+":revs:"+newRev, newRevData); + db.set("pad:" + this.id + ":revs:" + newRev, newRevData); this.saveToDatabase(); // set the author to pad - if(author) + if (author) { authorManager.addPad(author, this.id); + } if (this.head == 0) { hooks.callAll("padCreate", {'pad':this, 'author': author}); @@ -111,49 +112,47 @@ Pad.prototype.appendRevision = function appendRevision(aChangeset, author) { } }; -//save all attributes to the database -Pad.prototype.saveToDatabase = function saveToDatabase(){ +// save all attributes to the database +Pad.prototype.saveToDatabase = function saveToDatabase() { var dbObject = {}; - for(var attr in this){ - if(typeof this[attr] === "function") continue; - if(attributeBlackList.indexOf(attr) !== -1) continue; + for (var attr in this) { + if (typeof this[attr] === "function") continue; + if (attributeBlackList.indexOf(attr) !== -1) continue; dbObject[attr] = this[attr]; - if(jsonableList.indexOf(attr) !== -1){ + if (jsonableList.indexOf(attr) !== -1) { dbObject[attr] = dbObject[attr].toJsonable(); } } - db.set("pad:"+this.id, dbObject); + db.set("pad:" + this.id, dbObject); } // get time of last edit (changeset application) -Pad.prototype.getLastEdit = function getLastEdit(callback){ +Pad.prototype.getLastEdit = function getLastEdit(callback) { var revNum = this.getHeadRevisionNumber(); - db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "timestamp"], callback); + db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "timestamp"], callback); } Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum, callback) { - db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback); + db.getSub("pad:" + this.id + ":revs:" + revNum, ["changeset"], callback); }; Pad.prototype.getRevisionAuthor = function getRevisionAuthor(revNum, callback) { - db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "author"], callback); + db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "author"], callback); }; Pad.prototype.getRevisionDate = function getRevisionDate(revNum, callback) { - db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "timestamp"], callback); + db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "timestamp"], callback); }; Pad.prototype.getAllAuthors = function getAllAuthors() { var authors = []; - for(var key in this.pool.numToAttrib) - { - if(this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "") - { + for(var key in this.pool.numToAttrib) { + if (this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "") { authors.push(this.pool.numToAttrib[key][1]); } } @@ -168,26 +167,22 @@ Pad.prototype.getInternalRevisionAText = function getInternalRevisionAText(targe var atext; var changesets = []; - //find out which changesets are needed + // find out which changesets are needed var neededChangesets = []; var curRev = keyRev; - while (curRev < targetRev) - { + while (curRev < targetRev) { curRev++; neededChangesets.push(curRev); } async.series([ - //get all needed data out of the database - function(callback) - { + // get all needed data out of the database + function(callback) { async.parallel([ - //get the atext of the key revision - function (callback) - { - db.getSub("pad:"+_this.id+":revs:"+keyRev, ["meta", "atext"], function(err, _atext) - { - if(ERR(err, callback)) return; + // get the atext of the key revision + function (callback) { + db.getSub("pad:" + _this.id + ":revs:" + keyRev, ["meta", "atext"], function(err, _atext) { + if (ERR(err, callback)) return; try { atext = Changeset.cloneAText(_atext); } catch (e) { @@ -197,14 +192,12 @@ Pad.prototype.getInternalRevisionAText = function getInternalRevisionAText(targe callback(); }); }, - //get all needed changesets - function (callback) - { - async.forEach(neededChangesets, function(item, callback) - { - _this.getRevisionChangeset(item, function(err, changeset) - { - if(ERR(err, callback)) return; + + // get all needed changesets + function (callback) { + async.forEach(neededChangesets, function(item, callback) { + _this.getRevisionChangeset(item, function(err, changeset) { + if (ERR(err, callback)) return; changesets[item] = changeset; callback(); }); @@ -212,52 +205,53 @@ Pad.prototype.getInternalRevisionAText = function getInternalRevisionAText(targe } ], callback); }, - //apply all changesets to the key changeset - function(callback) - { + + // apply all changesets to the key changeset + function(callback) { var apool = _this.apool(); var curRev = keyRev; - while (curRev < targetRev) - { + while (curRev < targetRev) { curRev++; var cs = changesets[curRev]; - try{ + try { atext = Changeset.applyToAText(cs, atext, apool); - }catch(e) { + } catch(e) { return callback(e) } } callback(null); } - ], function(err) - { - if(ERR(err, callback)) return; + ], + function(err) { + if (ERR(err, callback)) return; callback(null, atext); }); }; Pad.prototype.getRevision = function getRevisionChangeset(revNum, callback) { - db.get("pad:"+this.id+":revs:"+revNum, callback); + db.get("pad:" + this.id + ":revs:" + revNum, callback); }; -Pad.prototype.getAllAuthorColors = function getAllAuthorColors(callback){ +Pad.prototype.getAllAuthorColors = function getAllAuthorColors(callback) { var authors = this.getAllAuthors(); var returnTable = {}; var colorPalette = authorManager.getColorPalette(); - async.forEach(authors, function(author, callback){ - authorManager.getAuthorColorId(author, function(err, colorId){ - if(err){ + async.forEach(authors, function(author, callback) { + authorManager.getAuthorColorId(author, function(err, colorId) { + if (err) { return callback(err); } - //colorId might be a hex color or an number out of the palette - returnTable[author]=colorPalette[colorId] || colorId; + + // colorId might be a hex color or an number out of the palette + returnTable[author] = colorPalette[colorId] || colorId; callback(); }); - }, function(err){ + }, + function(err) { callback(err, returnTable); }); }; @@ -266,15 +260,18 @@ Pad.prototype.getValidRevisionRange = function getValidRevisionRange(startRev, e startRev = parseInt(startRev, 10); var head = this.getHeadRevisionNumber(); endRev = endRev ? parseInt(endRev, 10) : head; - if(isNaN(startRev) || startRev < 0 || startRev > head) { + + if (isNaN(startRev) || startRev < 0 || startRev > head) { startRev = null; } - if(isNaN(endRev) || endRev < startRev) { + + if (isNaN(endRev) || endRev < startRev) { endRev = null; - } else if(endRev > head) { + } else if (endRev > head) { endRev = head; } - if(startRev !== null && endRev !== null) { + + if (startRev !== null && endRev !== null) { return { startRev: startRev , endRev: endRev } } return null; @@ -289,12 +286,12 @@ Pad.prototype.text = function text() { }; Pad.prototype.setText = function setText(newText) { - //clean the new text + // clean the new text newText = exports.cleanText(newText); var oldText = this.text(); - //create the changeset + // create the changeset // We want to ensure the pad still ends with a \n, but otherwise keep // getText() and setText() consistent. var changeset; @@ -304,27 +301,27 @@ Pad.prototype.setText = function setText(newText) { changeset = Changeset.makeSplice(oldText, 0, oldText.length-1, newText); } - //append the changeset + // append the changeset this.appendRevision(changeset); }; Pad.prototype.appendText = function appendText(newText) { - //clean the new text + // clean the new text newText = exports.cleanText(newText); var oldText = this.text(); - //create the changeset + // create the changeset var changeset = Changeset.makeSplice(oldText, oldText.length, 0, newText); - //append the changeset + // append the changeset this.appendRevision(changeset); }; Pad.prototype.appendChatMessage = function appendChatMessage(text, userId, time) { this.chatHead++; - //save the chat entry in the database - db.set("pad:"+this.id+":chat:"+this.chatHead, {"text": text, "userId": userId, "time": time}); + // save the chat entry in the database + db.set("pad:" + this.id + ":chat:" + this.chatHead, { "text": text, "userId": userId, "time": time }); this.saveToDatabase(); }; @@ -333,77 +330,70 @@ Pad.prototype.getChatMessage = function getChatMessage(entryNum, callback) { var entry; async.series([ - //get the chat entry - function(callback) - { - db.get("pad:"+_this.id+":chat:"+entryNum, function(err, _entry) - { - if(ERR(err, callback)) return; + // get the chat entry + function(callback) { + db.get("pad:" + _this.id + ":chat:" + entryNum, function(err, _entry) { + if (ERR(err, callback)) return; entry = _entry; callback(); }); }, - //add the authorName - function(callback) - { - //this chat message doesn't exist, return null - if(entry == null) - { + + // add the authorName + function(callback) { + // this chat message doesn't exist, return null + if (entry == null) { callback(); return; } - //get the authorName - authorManager.getAuthorName(entry.userId, function(err, authorName) - { - if(ERR(err, callback)) return; + // get the authorName + authorManager.getAuthorName(entry.userId, function(err, authorName) { + if (ERR(err, callback)) return; entry.userName = authorName; callback(); }); } - ], function(err) - { - if(ERR(err, callback)) return; + ], + function(err) { + if (ERR(err, callback)) return; callback(null, entry); }); }; Pad.prototype.getChatMessages = function getChatMessages(start, end, callback) { - //collect the numbers of chat entries and in which order we need them + // collect the numbers of chat entries and in which order we need them var neededEntries = []; var order = 0; - for(var i=start;i<=end; i++) - { - neededEntries.push({entryNum:i, order: order}); + for (var i = start; i <= end; i++) { + neededEntries.push({ entryNum: i, order: order }); order++; } var _this = this; - //get all entries out of the database + // get all entries out of the database var entries = []; - async.forEach(neededEntries, function(entryObject, callback) - { - _this.getChatMessage(entryObject.entryNum, function(err, entry) - { - if(ERR(err, callback)) return; + async.forEach(neededEntries, function(entryObject, callback) { + _this.getChatMessage(entryObject.entryNum, function(err, entry) { + if (ERR(err, callback)) return; entries[entryObject.order] = entry; callback(); }); - }, function(err) - { - if(ERR(err, callback)) return; + }, + function(err) { + if (ERR(err, callback)) return; - //sort out broken chat entries - //it looks like in happend in the past that the chat head was - //incremented, but the chat message wasn't added + // sort out broken chat entries + // it looks like in happened in the past that the chat head was + // incremented, but the chat message wasn't added var cleanedEntries = []; - for(var i=0;i delete the entry of this pad in the group - function(callback) - { - if(padID.indexOf("$") === -1) - { + // is it a group pad? -> delete the entry of this pad in the group + function(callback) { + if (padID.indexOf("$") === -1) { // it isn't a group pad, nothing to do here callback(); return; } // it is a group pad - var groupID = padID.substring(0,padID.indexOf("$")); + var groupID = padID.substring(0, padID.indexOf("$")); - db.get("group:" + groupID, function (err, group) - { - if(ERR(err, callback)) return; + db.get("group:" + groupID, function (err, group) { + if (ERR(err, callback)) return; - //remove the pad entry + // remove the pad entry delete group.pads[padID]; - //set the new value + // set the new value db.set("group:" + groupID, group); callback(); }); }, - //remove the readonly entries - function(callback) - { - readOnlyManager.getReadOnlyId(padID, function(err, readonlyID) - { - if(ERR(err, callback)) return; + + // remove the readonly entries + function(callback) { + readOnlyManager.getReadOnlyId(padID, function(err, readonlyID) { + if (ERR(err, callback)) return; db.remove("pad2readonly:" + padID); db.remove("readonly2pad:" + readonlyID); @@ -656,37 +629,34 @@ Pad.prototype.remove = function remove(callback) { callback(); }); }, - //delete all chat messages - function(callback) - { + + // delete all chat messages + function(callback) { var chatHead = _this.chatHead; - for(var i=0;i<=chatHead;i++) - { - db.remove("pad:"+padID+":chat:"+i); + for (var i = 0; i <= chatHead; i++) { + db.remove("pad:" + padID + ":chat:" + i); } callback(); }, - //delete all revisions - function(callback) - { + + // delete all revisions + function(callback) { var revHead = _this.head; - for(var i=0;i<=revHead;i++) - { - db.remove("pad:"+padID+":revs:"+i); + for (var i = 0; i <= revHead; i++) { + db.remove("pad:" + padID + ":revs:" + i); } callback(); }, - //remove pad from all authors who contributed - function(callback) - { + + // remove pad from all authors who contributed + function(callback) { var authorIDs = _this.getAllAuthors(); - authorIDs.forEach(function (authorID) - { + authorIDs.forEach(function (authorID) { authorManager.removePad(authorID, padID); }); @@ -694,20 +664,21 @@ Pad.prototype.remove = function remove(callback) { } ], callback); }, - //delete the pad entry and delete pad from padManager - function(callback) - { + + // delete the pad entry and delete pad from padManager + function(callback) { padManager.removePad(padID); - hooks.callAll("padRemove", {'padID':padID}); + hooks.callAll("padRemove", { 'padID': padID }); callback(); } - ], function(err) - { - if(ERR(err, callback)) return; + ], + function(err) { + if (ERR(err, callback)) return; callback(); }); }; - //set in db + +// set in db Pad.prototype.setPublicStatus = function setPublicStatus(publicStatus) { this.publicStatus = publicStatus; this.saveToDatabase(); @@ -727,14 +698,14 @@ Pad.prototype.isPasswordProtected = function isPasswordProtected() { }; Pad.prototype.addSavedRevision = function addSavedRevision(revNum, savedById, label) { - //if this revision is already saved, return silently - for(var i in this.savedRevisions){ - if(this.savedRevisions[i] && this.savedRevisions[i].revNum === revNum){ + // if this revision is already saved, return silently + for (var i in this.savedRevisions) { + if (this.savedRevisions[i] && this.savedRevisions[i].revNum === revNum) { return; } } - //build the saved revision object + // build the saved revision object var savedRevision = {}; savedRevision.revNum = revNum; savedRevision.savedById = savedById; @@ -742,7 +713,7 @@ Pad.prototype.addSavedRevision = function addSavedRevision(revNum, savedById, la savedRevision.timestamp = Date.now(); savedRevision.id = randomString(10); - //save this new saved revision + // save this new saved revision this.savedRevisions.push(savedRevision); this.saveToDatabase(); }; @@ -753,19 +724,17 @@ Pad.prototype.getSavedRevisions = function getSavedRevisions() { /* Crypto helper methods */ -function hash(password, salt) -{ +function hash(password, salt) { var shasum = crypto.createHash('sha512'); shasum.update(password + salt); + return shasum.digest("hex") + "$" + salt; } -function generateSalt() -{ +function generateSalt() { return randomString(86); } -function compare(hashStr, password) -{ +function compare(hashStr, password) { return hash(password, hashStr.split("$")[1]) === hashStr; } diff --git a/src/node/db/PadManager.js b/src/node/db/PadManager.js index 035ef3e5e..b198de81c 100644 --- a/src/node/db/PadManager.js +++ b/src/node/db/PadManager.js @@ -23,7 +23,7 @@ var customError = require("../utils/customError"); var Pad = require("../db/Pad").Pad; var db = require("./DB").db; -/** +/** * A cache of all loaded Pads. * * Provides "get" and "set" functions, @@ -35,12 +35,11 @@ var db = require("./DB").db; * that's defined somewhere more sensible. */ var globalPads = { - get: function (name) { return this[':'+name]; }, - set: function (name, value) - { + get: function(name) { return this[':'+name]; }, + set: function(name, value) { this[':'+name] = value; }, - remove: function (name) { + remove: function(name) { delete this[':'+name]; } }; @@ -54,56 +53,63 @@ var padList = { list: [], sorted : false, initiated: false, - init: function(cb) - { - db.findKeys("pad:*", "*:*:*", function(err, dbData) - { - if(ERR(err, cb)) return; - if(dbData != null){ + init: function(cb) { + db.findKeys("pad:*", "*:*:*", function(err, dbData) { + if (ERR(err, cb)) return; + + if (dbData != null) { padList.initiated = true - dbData.forEach(function(val){ + dbData.forEach(function(val) { padList.addPad(val.replace(/pad:/,""),false); }); - cb && cb() + + cb && cb(); } }); + return this; }, load: function(cb) { - if(this.initiated) cb && cb() - else this.init(cb) + if (this.initiated) { + cb && cb(); + } else { + this.init(cb); + } }, /** * Returns all pads in alphabetical order as array. */ - getPads: function(cb){ + getPads: function(cb) { this.load(function() { - if(!padList.sorted){ + if (!padList.sorted) { padList.list = padList.list.sort(); padList.sorted = true; } + cb && cb(padList.list); }) }, - addPad: function(name) - { - if(!this.initiated) return; - if(this.list.indexOf(name) == -1){ + addPad: function(name) { + if (!this.initiated) return; + + if (this.list.indexOf(name) == -1) { this.list.push(name); - this.sorted=false; + this.sorted = false; } }, - removePad: function(name) - { - if(!this.initiated) return; + removePad: function(name) { + if (!this.initiated) return; + var index = this.list.indexOf(name); - if(index>-1){ - this.list.splice(index,1); - this.sorted=false; + + if (index > -1) { + this.list.splice(index, 1); + this.sorted = false; } } }; -//initialises the allknowing data structure + +// initialises the all-knowing data structure /** * An array of padId transformations. These represent changes in pad name policy over @@ -117,58 +123,56 @@ var padIdTransforms = [ /** * Returns a Pad Object with the callback * @param id A String with the id of the pad - * @param {Function} callback + * @param {Function} callback */ exports.getPad = function(id, text, callback) -{ - //check if this is a valid padId - if(!exports.isValidPadId(id)) - { - callback(new customError(id + " is not a valid padId","apierror")); +{ + // check if this is a valid padId + if (!exports.isValidPadId(id)) { + callback(new customError(id + " is not a valid padId", "apierror")); + return; } - - //make text an optional parameter - if(typeof text == "function") - { + + // make text an optional parameter + if (typeof text == "function") { callback = text; text = null; } - - //check if this is a valid text - if(text != null) - { - //check if text is a string - if(typeof text != "string") - { - callback(new customError("text is not a string","apierror")); + + // check if this is a valid text + if (text != null) { + // check if text is a string + if (typeof text != "string") { + callback(new customError("text is not a string", "apierror")); + return; } - - //check if text is less than 100k chars - if(text.length > 100000) - { - callback(new customError("text must be less than 100k chars","apierror")); + + // check if text is less than 100k chars + if (text.length > 100000) { + callback(new customError("text must be less than 100k chars", "apierror")); + return; } } - + var pad = globalPads.get(id); - - //return pad if its already loaded - if(pad != null) - { + + // return pad if it's already loaded + if (pad != null) { callback(null, pad); + return; } - //try to load pad + // try to load pad pad = new Pad(id); - //initalize the pad - pad.init(text, function(err) - { - if(ERR(err, callback)) return; + // initalize the pad + pad.init(text, function(err) { + if (ERR(err, callback)) return; + globalPads.set(id, pad); padList.addPad(id); callback(null, pad); @@ -182,49 +186,48 @@ exports.listAllPads = function(cb) }); } -//checks if a pad exists +// checks if a pad exists exports.doesPadExists = function(padId, callback) { - db.get("pad:"+padId, function(err, value) - { - if(ERR(err, callback)) return; - if(value != null && value.atext){ + db.get("pad:" + padId, function(err, value) { + if (ERR(err, callback)) return; + + if (value != null && value.atext) { callback(null, true); - } - else - { - callback(null, false); + } else { + callback(null, false); } }); } -//returns a sanitized padId, respecting legacy pad id formats +// returns a sanitized padId, respecting legacy pad id formats exports.sanitizePadId = function(padId, callback) { var transform_index = arguments[2] || 0; - //we're out of possible transformations, so just return it - if(transform_index >= padIdTransforms.length) - { + + // we're out of possible transformations, so just return it + if (transform_index >= padIdTransforms.length) { callback(padId); + return; } - //check if padId exists - exports.doesPadExists(padId, function(junk, exists) - { - if(exists) - { + // check if padId exists + exports.doesPadExists(padId, function(junk, exists) { + if (exists) { callback(padId); + return; } - //get the next transformation *that's different* + // get the next transformation *that's different* var transformedPadId = padId; - while(transformedPadId == padId && transform_index < padIdTransforms.length) - { + + while(transformedPadId == padId && transform_index < padIdTransforms.length) { transformedPadId = padId.replace(padIdTransforms[transform_index][0], padIdTransforms[transform_index][1]); transform_index += 1; } - //check the next transform + + // check the next transform exports.sanitizePadId(transformedPadId, callback, transform_index); }); } @@ -237,13 +240,13 @@ exports.isValidPadId = function(padId) /** * Removes the pad from database and unloads it. */ -exports.removePad = function(padId){ - db.remove("pad:"+padId); +exports.removePad = function(padId) { + db.remove("pad:" + padId); exports.unloadPad(padId); padList.removePad(padId); } -//removes a pad from the cache +// removes a pad from the cache exports.unloadPad = function(padId) { globalPads.remove(padId); diff --git a/src/node/db/ReadOnlyManager.js b/src/node/db/ReadOnlyManager.js index f49f71e23..ac7bee045 100644 --- a/src/node/db/ReadOnlyManager.js +++ b/src/node/db/ReadOnlyManager.js @@ -29,43 +29,40 @@ var randomString = require("../utils/randomstring"); * @param {String} padId the id of the pad */ exports.getReadOnlyId = function (padId, callback) -{ +{ var readOnlyId; - + async.waterfall([ - //check if there is a pad2readonly entry - function(callback) - { + // check if there is a pad2readonly entry + function(callback) { db.get("pad2readonly:" + padId, callback); }, - function(dbReadOnlyId, callback) - { - //there is no readOnly Entry in the database, let's create one - if(dbReadOnlyId == null) - { + + function(dbReadOnlyId, callback) { + if (dbReadOnlyId == null) { + // there is no readOnly Entry in the database, let's create one readOnlyId = "r." + randomString(16); - + db.set("pad2readonly:" + padId, readOnlyId); db.set("readonly2pad:" + readOnlyId, padId); - } - //there is a readOnly Entry in the database, let's take this one - else - { + } else { + // there is a readOnly Entry in the database, let's take this one readOnlyId = dbReadOnlyId; } - + callback(); } - ], function(err) - { - if(ERR(err, callback)) return; - //return the results + ], + function(err) { + if (ERR(err, callback)) return; + + // return the results callback(null, readOnlyId); }) } /** - * returns a the padId for a read only id + * returns the padId for a read only id * @param {String} readOnlyId read only id */ exports.getPadId = function(readOnlyId, callback) @@ -74,20 +71,21 @@ exports.getPadId = function(readOnlyId, callback) } /** - * returns a the padId and readonlyPadId in an object for any id + * returns the padId and readonlyPadId in an object for any id * @param {String} padIdOrReadonlyPadId read only id or real pad id */ exports.getIds = function(id, callback) { - if (id.indexOf("r.") == 0) + if (id.indexOf("r.") == 0) { exports.getPadId(id, function (err, value) { - if(ERR(err, callback)) return; + if (ERR(err, callback)) return; + callback(null, { readOnlyPadId: id, padId: value, // Might be null, if this is an unknown read-only id readonly: true }); }); - else + } else { exports.getReadOnlyId(id, function (err, value) { callback(null, { readOnlyPadId: value, @@ -95,4 +93,5 @@ exports.getIds = function(id, callback) { readonly: false }); }); + } } diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.js index 2c46ac508..117f2794f 100644 --- a/src/node/db/SecurityManager.js +++ b/src/node/db/SecurityManager.js @@ -18,7 +18,6 @@ * limitations under the License. */ - var ERR = require("async-stacktrace"); var async = require("async"); var authorManager = require("./AuthorManager"); @@ -34,59 +33,57 @@ var authLogger = log4js.getLogger("auth"); * @param padID the pad the user wants to access * @param sessionCookie the session the user has (set via api) * @param token the token of the author (randomly generated at client side, used for public pads) - * @param password the password the user has given to access this pad, can be null + * @param password the password the user has given to access this pad, can be null * @param callback will be called with (err, {accessStatus: grant|deny|wrongPassword|needPassword, authorID: a.xxxxxx}) - */ -exports.checkAccess = function (padID, sessionCookie, token, password, callback) -{ + */ +exports.checkAccess = function(padID, sessionCookie, token, password, callback) +{ var statusObject; - - if(!padID) { + + if (!padID) { callback(null, {accessStatus: "deny"}); return; } // allow plugins to deny access var deniedByHook = hooks.callAll("onAccessCheck", {'padID': padID, 'password': password, 'token': token, 'sessionCookie': sessionCookie}).indexOf(false) > -1; - if(deniedByHook) - { + if (deniedByHook) { callback(null, {accessStatus: "deny"}); return; } - // a valid session is required (api-only mode) - if(settings.requireSession) - { - // without sessionCookie, access is denied - if(!sessionCookie) - { + if (settings.requireSession) { + // a valid session is required (api-only mode) + if (!sessionCookie) { + // without sessionCookie, access is denied callback(null, {accessStatus: "deny"}); + return; } - } - // a session is not required, so we'll check if it's a public pad - else - { - // it's not a group pad, means we can grant access - if(padID.indexOf("$") == -1) - { - //get author for this token - authorManager.getAuthor4Token(token, function(err, author) - { - if(ERR(err, callback)) return; - + } else { + // a session is not required, so we'll check if it's a public pad + if (padID.indexOf("$") == -1) { + // it's not a group pad, means we can grant access + + // get author for this token + authorManager.getAuthor4Token(token, function(err, author) { + if (ERR(err, callback)) return; + // assume user has access - statusObject = {accessStatus: "grant", authorID: author}; - // user can't create pads - if(settings.editOnly) - { + statusObject = { accessStatus: "grant", authorID: author }; + + if (settings.editOnly) { + // user can't create pads + // check if pad exists - padManager.doesPadExists(padID, function(err, exists) - { - if(ERR(err, callback)) return; - - // pad doesn't exist - user can't have access - if(!exists) statusObject.accessStatus = "deny"; + padManager.doesPadExists(padID, function(err, exists) { + if (ERR(err, callback)) return; + + if (!exists) { + // pad doesn't exist - user can't have access + statusObject.accessStatus = "deny"; + } + // grant or deny access, with author of token callback(null, statusObject); }); @@ -98,12 +95,12 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) // grant access, with author of token callback(null, statusObject); }); - - //don't continue + + // don't continue return; } } - + var groupID = padID.split("$")[0]; var padExists = false; var validSession = false; @@ -114,216 +111,193 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) var passwordStatus = password == null ? "notGiven" : "wrong"; // notGiven, correct, wrong async.series([ - //get basic informations from the database - function(callback) - { + // get basic informations from the database + function(callback) { async.parallel([ - //does pad exists - function(callback) - { - padManager.doesPadExists(padID, function(err, exists) - { - if(ERR(err, callback)) return; + // does pad exist + function(callback) { + padManager.doesPadExists(padID, function(err, exists) { + if (ERR(err, callback)) return; + padExists = exists; callback(); }); }, - //get information about all sessions contained in this cookie - function(callback) - { - if (!sessionCookie) - { + + // get information about all sessions contained in this cookie + function(callback) { + if (!sessionCookie) { callback(); return; } - + var sessionIDs = sessionCookie.split(','); - async.forEach(sessionIDs, function(sessionID, callback) - { - sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) - { - //skip session if it doesn't exist - if(err && err.message == "sessionID does not exist") - { + + async.forEach(sessionIDs, function(sessionID, callback) { + sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) { + // skip session if it doesn't exist + if (err && err.message == "sessionID does not exist") { authLogger.debug("Auth failed: unknown session"); callback(); + return; } - - if(ERR(err, callback)) return; - + + if (ERR(err, callback)) return; + var now = Math.floor(Date.now()/1000); - - //is it for this group? - if(sessionInfo.groupID != groupID) - { + + // is it for this group? + if (sessionInfo.groupID != groupID) { authLogger.debug("Auth failed: wrong group"); callback(); + return; } - - //is validUntil still ok? - if(sessionInfo.validUntil <= now) - { + + // is validUntil still ok? + if (sessionInfo.validUntil <= now) { authLogger.debug("Auth failed: validUntil"); callback(); + return; } - + // There is a valid session validSession = true; sessionAuthor = sessionInfo.authorID; - + callback(); }); }, callback); }, - //get author for token - function(callback) - { - //get author for this token - authorManager.getAuthor4Token(token, function(err, author) - { - if(ERR(err, callback)) return; + + // get author for token + function(callback) { + // get author for this token + authorManager.getAuthor4Token(token, function(err, author) { + if (ERR(err, callback)) return; + tokenAuthor = author; callback(); }); } ], callback); }, - //get more informations of this pad, if avaiable - function(callback) - { - //skip this if the pad doesn't exists - if(padExists == false) - { + + // get more informations of this pad, if avaiable + function(callback) { + // skip this if the pad doesn't exist + if (padExists == false) { callback(); + return; } - - padManager.getPad(padID, function(err, pad) - { - if(ERR(err, callback)) return; - - //is it a public pad? + + padManager.getPad(padID, function(err, pad) { + if (ERR(err, callback)) return; + + // is it a public pad? isPublic = pad.getPublicStatus(); - - //is it password protected? + + // is it password protected? isPasswordProtected = pad.isPasswordProtected(); - - //is password correct? - if(isPasswordProtected && password && pad.isCorrectPassword(password)) - { + + // is password correct? + if (isPasswordProtected && password && pad.isCorrectPassword(password)) { passwordStatus = "correct"; } - + callback(); }); }, - function(callback) - { - //- a valid session for this group is avaible AND pad exists - if(validSession && padExists) - { - //- the pad is not password protected - if(!isPasswordProtected) - { - //--> grant access - statusObject = {accessStatus: "grant", authorID: sessionAuthor}; - } - //- the setting to bypass password validation is set - else if(settings.sessionNoPassword) - { - //--> grant access - statusObject = {accessStatus: "grant", authorID: sessionAuthor}; - } - //- the pad is password protected and password is correct - else if(isPasswordProtected && passwordStatus == "correct") - { - //--> grant access - statusObject = {accessStatus: "grant", authorID: sessionAuthor}; - } - //- the pad is password protected but wrong password given - else if(isPasswordProtected && passwordStatus == "wrong") - { - //--> deny access, ask for new password and tell them that the password is wrong - statusObject = {accessStatus: "wrongPassword"}; - } - //- the pad is password protected but no password given - else if(isPasswordProtected && passwordStatus == "notGiven") - { - //--> ask for password - statusObject = {accessStatus: "needPassword"}; - } - else - { + + function(callback) { + if (validSession && padExists) { + // - a valid session for this group is avaible AND pad exists + if (!isPasswordProtected) { + // - the pad is not password protected + + // --> grant access + statusObject = { accessStatus: "grant", authorID: sessionAuthor }; + } else if (settings.sessionNoPassword) { + // - the setting to bypass password validation is set + + // --> grant access + statusObject = { accessStatus: "grant", authorID: sessionAuthor }; + } else if (isPasswordProtected && passwordStatus == "correct") { + // - the pad is password protected and password is correct + + // --> grant access + statusObject = { accessStatus: "grant", authorID: sessionAuthor }; + } else if (isPasswordProtected && passwordStatus == "wrong") { + // - the pad is password protected but wrong password given + + // --> deny access, ask for new password and tell them that the password is wrong + statusObject = { accessStatus: "wrongPassword" }; + } else if (isPasswordProtected && passwordStatus == "notGiven") { + // - the pad is password protected but no password given + + // --> ask for password + statusObject = { accessStatus: "needPassword" }; + } else { throw new Error("Ops, something wrong happend"); } - } - //- a valid session for this group avaible but pad doesn't exists - else if(validSession && !padExists) - { - //--> grant access + } else if (validSession && !padExists) { + // - a valid session for this group avaible but pad doesn't exist + + // --> grant access statusObject = {accessStatus: "grant", authorID: sessionAuthor}; - //--> deny access if user isn't allowed to create the pad - if(settings.editOnly) - { + + if (settings.editOnly) { + // --> deny access if user isn't allowed to create the pad authLogger.debug("Auth failed: valid session & pad does not exist"); statusObject.accessStatus = "deny"; } - } - // there is no valid session avaiable AND pad exists - else if(!validSession && padExists) - { - //-- its public and not password protected - if(isPublic && !isPasswordProtected) - { - //--> grant access, with author of token + } else if (!validSession && padExists) { + // there is no valid session avaiable AND pad exists + + // -- it's public and not password protected + if (isPublic && !isPasswordProtected) { + // --> grant access, with author of token statusObject = {accessStatus: "grant", authorID: tokenAuthor}; - } - //- its public and password protected and password is correct - else if(isPublic && isPasswordProtected && passwordStatus == "correct") - { - //--> grant access, with author of token + } else if (isPublic && isPasswordProtected && passwordStatus == "correct") { + // - it's public and password protected and password is correct + + // --> grant access, with author of token statusObject = {accessStatus: "grant", authorID: tokenAuthor}; - } - //- its public and the pad is password protected but wrong password given - else if(isPublic && isPasswordProtected && passwordStatus == "wrong") - { - //--> deny access, ask for new password and tell them that the password is wrong + } else if (isPublic && isPasswordProtected && passwordStatus == "wrong") { + // - it's public and the pad is password protected but wrong password given + + // --> deny access, ask for new password and tell them that the password is wrong statusObject = {accessStatus: "wrongPassword"}; - } - //- its public and the pad is password protected but no password given - else if(isPublic && isPasswordProtected && passwordStatus == "notGiven") - { - //--> ask for password + } else if (isPublic && isPasswordProtected && passwordStatus == "notGiven") { + // - it's public and the pad is password protected but no password given + + // --> ask for password statusObject = {accessStatus: "needPassword"}; - } - //- its not public - else if(!isPublic) - { + } else if (!isPublic) { + // - it's not public + authLogger.debug("Auth failed: invalid session & pad is not public"); - //--> deny access + // --> deny access statusObject = {accessStatus: "deny"}; - } - else - { + } else { throw new Error("Ops, something wrong happend"); } - } - // there is no valid session avaiable AND pad doesn't exists - else - { - authLogger.debug("Auth failed: invalid session & pad does not exist"); - //--> deny access - statusObject = {accessStatus: "deny"}; + } else { + // there is no valid session avaiable AND pad doesn't exist + authLogger.debug("Auth failed: invalid session & pad does not exist"); + // --> deny access + statusObject = {accessStatus: "deny"}; } - + callback(); } - ], function(err) - { - if(ERR(err, callback)) return; + ], + function(err) { + if (ERR(err, callback)) return; + callback(null, statusObject); }); }; diff --git a/src/node/db/SessionManager.js b/src/node/db/SessionManager.js index 99803ee71..6b580c033 100644 --- a/src/node/db/SessionManager.js +++ b/src/node/db/SessionManager.js @@ -17,7 +17,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - var ERR = require("async-stacktrace"); var customError = require("../utils/customError"); @@ -26,7 +25,7 @@ var db = require("./DB").db; var async = require("async"); var groupMangager = require("./GroupManager"); var authorMangager = require("./AuthorManager"); - + exports.doesSessionExist = function(sessionID, callback) { //check if the database entry of this session exists @@ -36,7 +35,7 @@ exports.doesSessionExist = function(sessionID, callback) callback(null, session != null); }); } - + /** * Creates a new session between an author and a group */ @@ -45,159 +44,148 @@ exports.createSession = function(groupID, authorID, validUntil, callback) var sessionID; async.series([ - //check if group exists + // check if the group exists function(callback) { groupMangager.doesGroupExist(groupID, function(err, exists) { if(ERR(err, callback)) return; - - //group does not exist - if(exists == false) - { - callback(new customError("groupID does not exist","apierror")); - } - //everything is fine, continue - else - { + + // group does not exist + if (exists == false) { + callback(new customError("groupID does not exist", "apierror")); + } else { + // everything is fine, continue callback(); } }); }, - //check if author exists + + // check if the author exists function(callback) { authorMangager.doesAuthorExists(authorID, function(err, exists) { if(ERR(err, callback)) return; - - //author does not exist - if(exists == false) - { - callback(new customError("authorID does not exist","apierror")); - } - //everything is fine, continue - else - { + + if (exists == false) { + // author does not exist + callback(new customError("authorID does not exist", "apierror")); + } else { + // everything is fine, continue callback(); } }); }, - //check validUntil and create the session db entry + + // check validUntil and create the session db entry function(callback) { - //check if rev is a number - if(typeof validUntil != "number") + // check if rev is a number + if (typeof validUntil != "number") { - //try to parse the number - if(isNaN(parseInt(validUntil))) - { - callback(new customError("validUntil is not a number","apierror")); + // try to parse the number + if (isNaN(parseInt(validUntil))) { + callback(new customError("validUntil is not a number", "apierror")); return; } validUntil = parseInt(validUntil); } - - //ensure this is not a negativ number - if(validUntil < 0) - { - callback(new customError("validUntil is a negativ number","apierror")); + + // ensure this is not a negative number + if (validUntil < 0) { + callback(new customError("validUntil is a negativ number", "apierror")); return; } - - //ensure this is not a float value - if(!is_int(validUntil)) - { - callback(new customError("validUntil is a float value","apierror")); + + // ensure this is not a float value + if (!is_int(validUntil)) { + callback(new customError("validUntil is a float value", "apierror")); return; } - - //check if validUntil is in the future - if(Math.floor(Date.now()/1000) > validUntil) - { - callback(new customError("validUntil is in the past","apierror")); + + // check if validUntil is in the future + if (Math.floor(Date.now()/1000) > validUntil) { + callback(new customError("validUntil is in the past", "apierror")); return; } - - //generate sessionID + + // generate sessionID sessionID = "s." + randomString(16); - - //set the session into the database + + // set the session into the database db.set("session:" + sessionID, {"groupID": groupID, "authorID": authorID, "validUntil": validUntil}); - + callback(); }, - //set the group2sessions entry + + // set the group2sessions entry function(callback) { - //get the entry + // get the entry db.get("group2sessions:" + groupID, function(err, group2sessions) { if(ERR(err, callback)) return; - - //the entry doesn't exist so far, let's create it - if(group2sessions == null || group2sessions.sessionIDs == null) - { + + if (group2sessions == null || group2sessions.sessionIDs == null) { + // the entry doesn't exist so far, let's create it group2sessions = {sessionIDs : {}}; } - - //add the entry for this session + + // add the entry for this session group2sessions.sessionIDs[sessionID] = 1; - - //save the new element back + + // save the new element back db.set("group2sessions:" + groupID, group2sessions); - + callback(); }); }, - //set the author2sessions entry + + // set the author2sessions entry function(callback) { - //get the entry + // get the entry db.get("author2sessions:" + authorID, function(err, author2sessions) { if(ERR(err, callback)) return; - - //the entry doesn't exist so far, let's create it - if(author2sessions == null || author2sessions.sessionIDs == null) - { + + if (author2sessions == null || author2sessions.sessionIDs == null) { + // the entry doesn't exist so far, let's create it author2sessions = {sessionIDs : {}}; } - - //add the entry for this session + + // add the entry for this session author2sessions.sessionIDs[sessionID] = 1; - + //save the new element back db.set("author2sessions:" + authorID, author2sessions); - + callback(); }); } ], function(err) { if(ERR(err, callback)) return; - - //return error and sessionID + + // return error and sessionID callback(null, {sessionID: sessionID}); }) } exports.getSessionInfo = function(sessionID, callback) { - //check if the database entry of this session exists + // check if the database entry of this session exists db.get("session:" + sessionID, function (err, session) { if(ERR(err, callback)) return; - - //session does not exists - if(session == null) - { - callback(new customError("sessionID does not exist","apierror")) - } - //everything is fine, return the sessioninfos - else - { + + if (session == null) { + // session does not exist + callback(new customError("sessionID does not exist", "apierror")) + } else { + // everything is fine, return the sessioninfos callback(null, session); } }); @@ -214,27 +202,25 @@ exports.deleteSession = function(sessionID, callback) async.series([ function(callback) { - //get the session entry + // get the session entry db.get("session:" + sessionID, function (err, session) { if(ERR(err, callback)) return; - - //session does not exists - if(session == null) - { - callback(new customError("sessionID does not exist","apierror")) - } - //everything is fine, return the sessioninfos - else - { + + if (session == null) { + // session does not exist + callback(new customError("sessionID does not exist", "apierror")) + } else { + // everything is fine, use the sessioninfos authorID = session.authorID; groupID = session.groupID; - + callback(); } }); }, - //get the group2sessions entry + + // get the group2sessions entry function(callback) { db.get("group2sessions:" + groupID, function (err, _group2sessions) @@ -244,7 +230,8 @@ exports.deleteSession = function(sessionID, callback) callback(); }); }, - //get the author2sessions entry + + // get the author2sessions entry function(callback) { db.get("author2sessions:" + authorID, function (err, _author2sessions) @@ -254,24 +241,25 @@ exports.deleteSession = function(sessionID, callback) callback(); }); }, - //remove the values from the database + + // remove the values from the database function(callback) { //remove the session db.remove("session:" + sessionID); - - //remove session from group2sessions + + // remove session from group2sessions if(group2sessions != null) { // Maybe the group was already deleted delete group2sessions.sessionIDs[sessionID]; db.set("group2sessions:" + groupID, group2sessions); } - //remove session from author2sessions + // remove session from author2sessions if(author2sessions != null) { // Maybe the author was already deleted delete author2sessions.sessionIDs[sessionID]; db.set("author2sessions:" + authorID, author2sessions); } - + callback(); } ], function(err) @@ -286,40 +274,34 @@ exports.listSessionsOfGroup = function(groupID, callback) groupMangager.doesGroupExist(groupID, function(err, exists) { if(ERR(err, callback)) return; - - //group does not exist - if(exists == false) - { - callback(new customError("groupID does not exist","apierror")); - } - //everything is fine, continue - else - { + + if (exists == false) { + // group does not exist + callback(new customError("groupID does not exist", "apierror")); + } else { + // everything is fine, continue listSessionsWithDBKey("group2sessions:" + groupID, callback); } }); } exports.listSessionsOfAuthor = function(authorID, callback) -{ +{ authorMangager.doesAuthorExists(authorID, function(err, exists) { if(ERR(err, callback)) return; - - //group does not exist - if(exists == false) - { - callback(new customError("authorID does not exist","apierror")); - } - //everything is fine, continue - else - { + + if (exists == false) { + // group does not exist + callback(new customError("authorID does not exist", "apierror")); + } else { + // everything is fine, continue listSessionsWithDBKey("author2sessions:" + authorID, callback); } }); } -//this function is basicly the code listSessionsOfAuthor and listSessionsOfGroup has in common +// this function is basically the code listSessionsOfAuthor and listSessionsOfGroup has in common function listSessionsWithDBKey (dbkey, callback) { var sessions; @@ -327,7 +309,7 @@ function listSessionsWithDBKey (dbkey, callback) async.series([ function(callback) { - //get the group2sessions entry + // get the group2sessions entry db.get(dbkey, function(err, sessionObject) { if(ERR(err, callback)) return; @@ -335,26 +317,24 @@ function listSessionsWithDBKey (dbkey, callback) callback(); }); }, + function(callback) - { - //collect all sessionIDs in an arrary + { + // collect all sessionIDs in an arrary var sessionIDs = []; for (var i in sessions) { sessionIDs.push(i); } - - //foreach trough the sessions and get the sessioninfos + + // iterate through the sessions and get the sessioninfos async.forEach(sessionIDs, function(sessionID, callback) { exports.getSessionInfo(sessionID, function(err, sessionInfo) { - if (err == "apierror: sessionID does not exist") - { + if (err == "apierror: sessionID does not exist") { console.warn(`Found bad session ${sessionID} in ${dbkey}`); - } - else if(ERR(err, callback)) - { + } else if(ERR(err, callback)) { return; } @@ -370,8 +350,8 @@ function listSessionsWithDBKey (dbkey, callback) }); } -//checks if a number is an int +// checks if a number is an int function is_int(value) -{ - return (parseFloat(value) == parseInt(value)) && !isNaN(value) +{ + return (parseFloat(value) == parseInt(value)) && !isNaN(value); } diff --git a/src/node/db/SessionStore.js b/src/node/db/SessionStore.js index 974046908..80d35f290 100644 --- a/src/node/db/SessionStore.js +++ b/src/node/db/SessionStore.js @@ -1,4 +1,4 @@ - /* +/* * Stores session data in the database * Source; https://github.com/edy-b/SciFlowWriter/blob/develop/available_plugins/ep_sciflowwriter/db/DirtyStore.js * This is not used for authors that are created via the API at current @@ -13,11 +13,12 @@ var SessionStore = module.exports = function SessionStore() {}; SessionStore.prototype.__proto__ = Store.prototype; -SessionStore.prototype.get = function(sid, fn){ +SessionStore.prototype.get = function(sid, fn) { messageLogger.debug('GET ' + sid); + var self = this; - db.get("sessionstorage:" + sid, function (err, sess) - { + + db.get("sessionstorage:" + sid, function(err, sess) { if (sess) { sess.cookie.expires = 'string' == typeof sess.cookie.expires ? new Date(sess.cookie.expires) : sess.cookie.expires; if (!sess.cookie.expires || new Date() < sess.cookie.expires) { @@ -31,26 +32,30 @@ SessionStore.prototype.get = function(sid, fn){ }); }; -SessionStore.prototype.set = function(sid, sess, fn){ +SessionStore.prototype.set = function(sid, sess, fn) { messageLogger.debug('SET ' + sid); + db.set("sessionstorage:" + sid, sess); - process.nextTick(function(){ - if(fn) fn(); + process.nextTick(function() { + if (fn) fn(); }); }; -SessionStore.prototype.destroy = function(sid, fn){ +SessionStore.prototype.destroy = function(sid, fn) { messageLogger.debug('DESTROY ' + sid); + db.remove("sessionstorage:" + sid); - process.nextTick(function(){ - if(fn) fn(); + process.nextTick(function() { + if (fn) fn(); }); }; -SessionStore.prototype.all = function(fn){ +SessionStore.prototype.all = function(fn) { messageLogger.debug('ALL'); + var sessions = []; - db.forEach(function(key, value){ + + db.forEach(function(key, value) { if (key.substr(0,15) === "sessionstorage:") { sessions.push(value); } @@ -58,20 +63,23 @@ SessionStore.prototype.all = function(fn){ fn(null, sessions); }; -SessionStore.prototype.clear = function(fn){ +SessionStore.prototype.clear = function(fn) { messageLogger.debug('CLEAR'); - db.forEach(function(key, value){ + + db.forEach(function(key, value) { if (key.substr(0,15) === "sessionstorage:") { db.db.remove("session:" + key); } }); - if(fn) fn(); + if (fn) fn(); }; -SessionStore.prototype.length = function(fn){ +SessionStore.prototype.length = function(fn) { messageLogger.debug('LENGTH'); + var i = 0; - db.forEach(function(key, value){ + + db.forEach(function(key, value) { if (key.substr(0,15) === "sessionstorage:") { i++; } diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index 6ec5907e2..3ed7aa4e1 100644 --- a/src/node/handler/APIHandler.js +++ b/src/node/handler/APIHandler.js @@ -32,19 +32,17 @@ var apiHandlerLogger = log4js.getLogger('APIHandler'); //ensure we have an apikey var apikey = null; var apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || "./APIKEY.txt"); -try -{ + +try { apikey = fs.readFileSync(apikeyFilename,"utf8"); apiHandlerLogger.info(`Api key file read from: "${apikeyFilename}"`); -} -catch(e) -{ +} catch(e) { apiHandlerLogger.info(`Api key file "${apikeyFilename}" not found. Creating with random contents.`); apikey = randomString(32); fs.writeFileSync(apikeyFilename,apikey,"utf8"); } -//a list of all functions +// a list of all functions var version = {}; version["1"] = Object.assign({}, @@ -156,106 +154,92 @@ exports.handle = function(apiVersion, functionName, fields, req, res) { //check if this is a valid apiversion var isKnownApiVersion = false; - for(var knownApiVersion in version) - { - if(knownApiVersion == apiVersion) - { + + for (var knownApiVersion in version) { + if (knownApiVersion == apiVersion) { isKnownApiVersion = true; break; } } - //say goodbye if this is an unknown API version - if(!isKnownApiVersion) - { + // say goodbye if this is an unknown API version + if (!isKnownApiVersion) { res.statusCode = 404; res.send({code: 3, message: "no such api version", data: null}); return; } - //check if this is a valid function name + // check if this is a valid function name var isKnownFunctionname = false; - for(var knownFunctionname in version[apiVersion]) - { - if(knownFunctionname == functionName) - { + + for (var knownFunctionname in version[apiVersion]) { + if (knownFunctionname == functionName) { isKnownFunctionname = true; break; } } - //say goodbye if this is a unknown function - if(!isKnownFunctionname) - { + // say goodbye if this is an unknown function + if (!isKnownFunctionname) { res.send({code: 3, message: "no such function", data: null}); return; } - //check the api key! + // check the api key! fields["apikey"] = fields["apikey"] || fields["api_key"]; - if(fields["apikey"] != apikey.trim()) - { + if (fields["apikey"] != apikey.trim()) { res.statusCode = 401; res.send({code: 4, message: "no or wrong API Key", data: null}); return; } - //sanitize any pad id's before continuing - if(fields["padID"]) - { - padManager.sanitizePadId(fields["padID"], function(padId) - { + // sanitize any padIDs before continuing + if (fields["padID"]) { + padManager.sanitizePadId(fields["padID"], function(padId) { fields["padID"] = padId; callAPI(apiVersion, functionName, fields, req, res); }); - } - else if(fields["padName"]) - { - padManager.sanitizePadId(fields["padName"], function(padId) - { + } else if (fields["padName"]) { + padManager.sanitizePadId(fields["padName"], function(padId) { fields["padName"] = padId; callAPI(apiVersion, functionName, fields, req, res); }); - } - else - { + } else { callAPI(apiVersion, functionName, fields, req, res); } } -//calls the api function +// calls the api function function callAPI(apiVersion, functionName, fields, req, res) { - //put the function parameters in an array + // put the function parameters in an array var functionParams = version[apiVersion][functionName].map(function (field) { return fields[field] - }) + }); - //add a callback function to handle the response - functionParams.push(function(err, data) - { - // no error happend, everything is fine - if(err == null) - { - if(!data) + // add a callback function to handle the response + functionParams.push(function(err, data) { + if (err == null) { + // no error happened, everything is fine + + if (!data) { data = null; + } res.send({code: 0, message: "ok", data: data}); - } - // parameters were wrong and the api stopped execution, pass the error - else if(err.name == "apierror") - { + } else if (err.name == "apierror") { + // parameters were wrong and the api stopped execution, pass the error + res.send({code: 1, message: err.message, data: null}); - } - //an unknown error happend - else - { + } else { + // an unknown error happened + res.send({code: 2, message: "internal error", data: null}); ERR(err); } }); - //call the api function + // call the api function api[functionName].apply(this, functionParams); } diff --git a/src/node/handler/ExportHandler.js b/src/node/handler/ExportHandler.js index db3d2d40d..3bcabf233 100644 --- a/src/node/handler/ExportHandler.js +++ b/src/node/handler/ExportHandler.js @@ -32,13 +32,15 @@ var TidyHtml = require('../utils/TidyHtml'); var convertor = null; -//load abiword only if its enabled -if(settings.abiword != null) +// load abiword only if it is enabled +if (settings.abiword != null) { convertor = require("../utils/Abiword"); +} // Use LibreOffice if an executable has been defined in the settings -if(settings.soffice != null) +if (settings.soffice != null) { convertor = require("../utils/LibreOffice"); +} const tempDirectory = os.tmpdir(); @@ -53,62 +55,55 @@ exports.doExport = function(req, res, padId, type) hooks.aCallFirst("exportFileName", padId, function(err, hookFileName){ // if fileName is set then set it to the padId, note that fileName is returned as an array. - if(hookFileName.length) fileName = hookFileName; + if (hookFileName.length) { + fileName = hookFileName; + } - //tell the browser that this is a downloadable file + // tell the browser that this is a downloadable file res.attachment(fileName + "." + type); - //if this is a plain text export, we can do this directly + // if this is a plain text export, we can do this directly // We have to over engineer this because tabs are stored as attributes and not plain text - if(type == "etherpad"){ - exportEtherpad.getPadRaw(padId, function(err, pad){ - if(!err){ + if (type == "etherpad") { + exportEtherpad.getPadRaw(padId, function(err, pad) { + if (!err) { res.send(pad); // return; } }); - } - else if(type == "txt") - { - exporttxt.getPadTXTDocument(padId, req.params.rev, function(err, txt) - { - if(!err) { + } else if (type == "txt") { + exporttxt.getPadTXTDocument(padId, req.params.rev, function(err, txt) { + if (!err) { res.send(txt); } }); - } - else - { + } else { var html; var randNum; var srcFile, destFile; async.series([ - //render the html document - function(callback) - { - exporthtml.getPadHTMLDocument(padId, req.params.rev, function(err, _html) - { - if(ERR(err, callback)) return; + // render the html document + function(callback) { + exporthtml.getPadHTMLDocument(padId, req.params.rev, function(err, _html) { + if (ERR(err, callback)) return; html = _html; callback(); }); }, - //decide what to do with the html export - function(callback) - { - //if this is a html export, we can send this from here directly - if(type == "html") - { + + // decide what to do with the html export + function(callback) { + // if this is a html export, we can send this from here directly + if (type == "html") { // do any final changes the plugin might want to make - hooks.aCallFirst("exportHTMLSend", html, function(err, newHTML){ - if(newHTML.length) html = newHTML; + hooks.aCallFirst("exportHTMLSend", html, function(err, newHTML) { + if (newHTML.length) html = newHTML; res.send(html); callback("stop"); }); - } - else //write the html export to a file - { + } else { + // write the html export to a file randNum = Math.floor(Math.random()*0xFFFFFFFF); srcFile = tempDirectory + "/etherpad_export_" + randNum + ".html"; fs.writeFile(srcFile, html, callback); @@ -116,64 +111,56 @@ exports.doExport = function(req, res, padId, type) }, // Tidy up the exported HTML - function(callback) - { - //ensure html can be collected by the garbage collector + function(callback) { + // ensure html can be collected by the garbage collector html = null; TidyHtml.tidy(srcFile, callback); }, - //send the convert job to the convertor (abiword, libreoffice, ..) - function(callback) - { + // send the convert job to the convertor (abiword, libreoffice, ..) + function(callback) { destFile = tempDirectory + "/etherpad_export_" + randNum + "." + type; // Allow plugins to overwrite the convert in export process - hooks.aCallAll("exportConvert", {srcFile: srcFile, destFile: destFile, req: req, res: res}, function(err, result){ - if(!err && result.length > 0){ + hooks.aCallAll("exportConvert", { srcFile: srcFile, destFile: destFile, req: req, res: res }, function(err, result) { + if (!err && result.length > 0) { // console.log("export handled by plugin", destFile); handledByPlugin = true; callback(); - }else{ + } else { convertor.convertFile(srcFile, destFile, type, callback); } }); }, - //send the file - function(callback) - { + + // send the file + function(callback) { res.sendFile(destFile, null, callback); }, - //clean up temporary files - function(callback) - { + + // clean up temporary files + function(callback) { async.parallel([ - function(callback) - { + function(callback) { fs.unlink(srcFile, callback); }, - function(callback) - { - //100ms delay to accomidate for slow windows fs - if(os.type().indexOf("Windows") > -1) - { - setTimeout(function() - { + function(callback) { + // 100ms delay to accommodate for slow windows fs + if (os.type().indexOf("Windows") > -1) { + setTimeout(function() { fs.unlink(destFile, callback); }, 100); - } - else - { + } else { fs.unlink(destFile, callback); } } ], callback); } - ], function(err) - { - if(err && err != "stop") ERR(err); + ], + function(err) { + if (err && err != "stop") ERR(err); }) } } diff --git a/src/node/handler/ImportHandler.js b/src/node/handler/ImportHandler.js index 30b773972..dae71bf6d 100644 --- a/src/node/handler/ImportHandler.js +++ b/src/node/handler/ImportHandler.js @@ -37,29 +37,30 @@ var ERR = require("async-stacktrace") var convertor = null; var exportExtension = "htm"; -//load abiword only if its enabled and if soffice is disabled -if(settings.abiword != null && settings.soffice === null) +// load abiword only if it is enabled and if soffice is disabled +if (settings.abiword != null && settings.soffice === null) { convertor = require("../utils/Abiword"); +} -//load soffice only if its enabled -if(settings.soffice != null) { +// load soffice only if it is enabled +if (settings.soffice != null) { convertor = require("../utils/LibreOffice"); exportExtension = "html"; } const tmpDirectory = os.tmpdir(); - + /** * do a requested import - */ + */ exports.doImport = function(req, res, padId) { var apiLogger = log4js.getLogger("ImportHandler"); - //pipe to a file - //convert file to html via abiword or soffice - //set html in the pad - + // pipe to a file + // convert file to html via abiword or soffice + // set html in the pad + var srcFile, destFile , pad , text @@ -68,69 +69,74 @@ exports.doImport = function(req, res, padId) , useConvertor; var randNum = Math.floor(Math.random()*0xFFFFFFFF); - + // setting flag for whether to use convertor or not useConvertor = (convertor != null); async.series([ - //save the uploaded file to /tmp + // save the uploaded file to /tmp function(callback) { var form = new formidable.IncomingForm(); form.keepExtensions = true; form.uploadDir = tmpDirectory; - - form.parse(req, function(err, fields, files) { - //the upload failed, stop at this point - if(err || files.file === undefined) { - if(err) console.warn("Uploading Error: " + err.stack); + + form.parse(req, function(err, fields, files) { + if (err || files.file === undefined) { + // the upload failed, stop at this point + if (err) { + console.warn("Uploading Error: " + err.stack); + } callback("uploadFailed"); return; } - //everything ok, continue - //save the path of the uploaded file + // everything ok, continue + // save the path of the uploaded file srcFile = files.file.path; callback(); }); }, - - //ensure this is a file ending we know, else we change the file ending to .txt - //this allows us to accept source code files like .c or .java + + // ensure this is a file ending we know, else we change the file ending to .txt + // this allows us to accept source code files like .c or .java function(callback) { var fileEnding = path.extname(srcFile).toLowerCase() , knownFileEndings = [".txt", ".doc", ".docx", ".pdf", ".odt", ".html", ".htm", ".etherpad", ".rtf"] , fileEndingKnown = (knownFileEndings.indexOf(fileEnding) > -1); - - //if the file ending is known, continue as normal - if(fileEndingKnown) { + + // if the file ending is known, continue as normal + if (fileEndingKnown) { callback(); - + return; } - //we need to rename this file with a .txt ending - if(settings.allowUnknownFileEnds === true){ + // we need to rename this file with a .txt ending + if (settings.allowUnknownFileEnds === true) { var oldSrcFile = srcFile; - srcFile = path.join(path.dirname(srcFile),path.basename(srcFile, fileEnding)+".txt"); + srcFile = path.join(path.dirname(srcFile), path.basename(srcFile, fileEnding) + ".txt"); fs.rename(oldSrcFile, srcFile, callback); - }else{ + } else { console.warn("Not allowing unknown file type to be imported", fileEnding); callback("uploadFailed"); } }, - function(callback){ + + function(callback) { destFile = path.join(tmpDirectory, "etherpad_import_" + randNum + "." + exportExtension); // Logic for allowing external Import Plugins - hooks.aCallAll("import", {srcFile: srcFile, destFile: destFile}, function(err, result){ - if(ERR(err, callback)) return callback(); - if(result.length > 0){ // This feels hacky and wrong.. + hooks.aCallAll("import", { srcFile: srcFile, destFile: destFile }, function(err, result) { + if (ERR(err, callback)) return callback(); + + if (result.length > 0) { // This feels hacky and wrong.. importHandledByPlugin = true; } callback(); }); }, + function(callback) { var fileEnding = path.extname(srcFile).toLowerCase() var fileIsNotEtherpad = (fileEnding !== ".etherpad"); @@ -141,23 +147,24 @@ exports.doImport = function(req, res, padId) return; } - // we do this here so we can see if the pad has quit ea few edits - padManager.getPad(padId, function(err, _pad){ + // we do this here so we can see if the pad has quite a few edits + padManager.getPad(padId, function(err, _pad) { var headCount = _pad.head; - if(headCount >= 10){ - apiLogger.warn("Direct database Import attempt of a pad that already has content, we wont be doing this") + if (headCount >= 10) { + apiLogger.warn("Direct database Import attempt of a pad that already has content, we wont be doing this"); return callback("padHasData"); } - fs.readFile(srcFile, "utf8", function(err, _text){ + fs.readFile(srcFile, "utf8", function(err, _text) { directDatabaseAccess = true; - importEtherpad.setPadRaw(padId, _text, function(err){ + importEtherpad.setPadRaw(padId, _text, function(err) { callback(); }); }); }); }, - //convert file to html + + // convert file to html if necessary function(callback) { if (importHandledByPlugin || directDatabaseAccess) { callback(); @@ -168,18 +175,20 @@ exports.doImport = function(req, res, padId) var fileEnding = path.extname(srcFile).toLowerCase(); var fileIsHTML = (fileEnding === ".html" || fileEnding === ".htm"); var fileIsTXT = (fileEnding === ".txt"); + if (fileIsTXT) useConvertor = false; // Don't use convertor for text files + // See https://github.com/ether/etherpad-lite/issues/2572 if (fileIsHTML || (useConvertor === false)) { // if no convertor only rename fs.rename(srcFile, destFile, callback); - + return; } convertor.convertFile(srcFile, destFile, exportExtension, function(err) { - //catch convert errors - if(err) { + // catch convert errors + if (err) { console.warn("Converting Error:", err); return callback("convertFailed"); } @@ -187,7 +196,7 @@ exports.doImport = function(req, res, padId) callback(); }); }, - + function(callback) { if (useConvertor || directDatabaseAccess) { callback(); @@ -216,17 +225,17 @@ exports.doImport = function(req, res, padId) callback(); }); }, - - //get the pad object + + // get the pad object function(callback) { - padManager.getPad(padId, function(err, _pad){ - if(ERR(err, callback)) return; + padManager.getPad(padId, function(err, _pad) { + if (ERR(err, callback)) return; pad = _pad; callback(); }); }, - - //read the text + + // read the text function(callback) { if (directDatabaseAccess) { callback(); @@ -234,43 +243,46 @@ exports.doImport = function(req, res, padId) return; } - fs.readFile(destFile, "utf8", function(err, _text){ - if(ERR(err, callback)) return; + fs.readFile(destFile, "utf8", function(err, _text) { + if (ERR(err, callback)) return; text = _text; // Title needs to be stripped out else it appends it to the pad.. text = text.replace("", "<!-- <title>"); text = text.replace("","-->"); - //node on windows has a delay on releasing of the file lock. - //We add a 100ms delay to work around this - if(os.type().indexOf("Windows") > -1){ + // node on windows has a delay on releasing of the file lock. + // We add a 100ms delay to work around this + if (os.type().indexOf("Windows") > -1) { setTimeout(function() {callback();}, 100); } else { callback(); } }); }, - - //change text of the pad and broadcast the changeset + + // change text of the pad and broadcast the changeset function(callback) { - if(!directDatabaseAccess){ + if (!directDatabaseAccess) { var fileEnding = path.extname(srcFile).toLowerCase(); if (importHandledByPlugin || useConvertor || fileEnding == ".htm" || fileEnding == ".html") { importHtml.setPadHTML(pad, text, function(e){ - if(e) apiLogger.warn("Error importing, possibly caused by malformed HTML"); + if (e) { + apiLogger.warn("Error importing, possibly caused by malformed HTML"); + } }); } else { pad.setText(text); } } - // Load the Pad into memory then brodcast updates to all clients + // Load the Pad into memory then broadcast updates to all clients padManager.unloadPad(padId); - padManager.getPad(padId, function(err, _pad){ + padManager.getPad(padId, function(err, _pad) { var pad = _pad; padManager.unloadPad(padId); + // direct Database Access means a pad user should perform a switchToPad - // and not attempt to recieve updated pad data.. + // and not attempt to receive updated pad data if (directDatabaseAccess) { callback(); @@ -283,8 +295,8 @@ exports.doImport = function(req, res, padId) }); }, - - //clean up temporary files + + // clean up temporary files function(callback) { if (directDatabaseAccess) { callback(); @@ -308,17 +320,16 @@ exports.doImport = function(req, res, padId) } ], function(err) { var status = "ok"; - - //check for known errors and replace the status - if(err == "uploadFailed" || err == "convertFailed" || err == "padHasData") - { + + // check for known errors and replace the status + if (err == "uploadFailed" || err == "convertFailed" || err == "padHasData") { status = err; err = null; } ERR(err); - //close the connection + // close the connection res.send( " \ \ @@ -331,4 +342,3 @@ exports.doImport = function(req, res, padId) ); }); } - diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 506b3aae4..5c20ed1b6 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -54,8 +54,8 @@ exports.sessioninfos = sessioninfos; // Measure total amount of users stats.gauge('totalUsers', function() { - return Object.keys(socketio.sockets.sockets).length -}) + return Object.keys(socketio.sockets.sockets).length; +}); /** * A changeset queue per pad that is processed by handleUserChanges() @@ -63,7 +63,7 @@ stats.gauge('totalUsers', function() { var padChannels = new channels.channels(handleUserChanges); /** - * Saves the Socket class we need to send and recieve data from the client + * Saves the Socket class we need to send and receive data from the client */ var socketio; @@ -84,7 +84,7 @@ exports.handleConnect = function(client) { stats.meter('connects').mark(); - //Initalize sessioninfos for this new session + // Initalize sessioninfos for this new session sessioninfos[client.id]={}; } @@ -97,11 +97,11 @@ exports.kickSessionsFromPad = function(padID) if(typeof socketio.sockets['clients'] !== 'function') return; - //skip if there is nobody on this pad + // skip if there is nobody on this pad if(_getRoomClients(padID).length == 0) return; - //disconnect everyone from this pad + // disconnect everyone from this pad socketio.sockets.in(padID).json.send({disconnect:"deleted"}); } @@ -113,29 +113,26 @@ exports.handleDisconnect = function(client) { stats.meter('disconnects').mark(); - //save the padname of this session + // save the padname of this session var session = sessioninfos[client.id]; - //if this connection was already etablished with a handshake, send a disconnect message to the others - if(session && session.author) - { - + // if this connection was already etablished with a handshake, send a disconnect message to the others + if (session && session.author) { // Get the IP address from our persistant object var ip = remoteAddress[client.id]; // Anonymize the IP address if IP logging is disabled - if(settings.disableIPlogging) { + if (settings.disableIPlogging) { ip = 'ANONYMOUS'; } - accessLogger.info('[LEAVE] Pad "'+session.padId+'": Author "'+session.author+'" on client '+client.id+' with IP "'+ip+'" left the pad') + accessLogger.info('[LEAVE] Pad "' + session.padId + '": Author "' + session.author + '" on client ' + client.id + ' with IP "' + ip + '" left the pad'); - //get the author color out of the db - authorManager.getAuthorColorId(session.author, function(err, color) - { + // get the author color out of the db + authorManager.getAuthorColorId(session.author, function(err, color) { ERR(err); - //prepare the notification for the other users on the pad, that this user left + // prepare the notification for the other users on the pad, that this user left var messageToTheOtherUsers = { "type": "COLLABROOM", "data": { @@ -149,7 +146,7 @@ exports.handleDisconnect = function(client) } }; - //Go trough all user that are still on the pad, and send them the USER_LEAVE message + // Go through all user that are still on the pad, and send them the USER_LEAVE message client.broadcast.to(session.padId).json.send(messageToTheOtherUsers); // Allow plugins to hook into users leaving the pad @@ -157,7 +154,7 @@ exports.handleDisconnect = function(client) }); } - //Delete the sessioninfos entrys of this session + // Delete the sessioninfos entrys of this session delete sessioninfos[client.id]; } @@ -168,23 +165,24 @@ exports.handleDisconnect = function(client) */ exports.handleMessage = function(client, message) { - if(message == null) - { + if (message == null) { return; } - if(!message.type) - { + + if (!message.type) { return; } - var thisSession = sessioninfos[client.id] - if(!thisSession) { + + var thisSession = sessioninfos[client.id]; + + if (!thisSession) { messageLogger.warn("Dropped message from an unknown connection.") return; } - var handleMessageHook = function(callback){ + var handleMessageHook = function(callback) { // Allow plugins to bypass the readonly message blocker - hooks.aCallAll("handleMessageSecurity", { client: client, message: message }, function ( err, messages ) { + hooks.aCallAll("handleMessageSecurity", { client: client, message: message }, function( err, messages ) { if(ERR(err, callback)) return; _.each(messages, function(newMessage){ if ( newMessage === true ) { @@ -196,7 +194,7 @@ exports.handleMessage = function(client, message) var dropMessage = false; // Call handleMessage hook. If a plugin returns null, the message will be dropped. Note that for all messages // handleMessage will be called, even if the client is not authorized - hooks.aCallAll("handleMessage", { client: client, message: message }, function ( err, messages ) { + hooks.aCallAll("handleMessage", { client: client, message: message }, function( err, messages ) { if(ERR(err, callback)) return; _.each(messages, function(newMessage){ if ( newMessage === null ) { @@ -210,18 +208,18 @@ exports.handleMessage = function(client, message) } - var finalHandler = function () { - //Check what type of message we get and delegate to the other methodes - if(message.type == "CLIENT_READY") { + var finalHandler = function() { + // Check what type of message we get and delegate to the other methods + if (message.type == "CLIENT_READY") { handleClientReady(client, message); - } else if(message.type == "CHANGESET_REQ") { + } else if (message.type == "CHANGESET_REQ") { handleChangesetRequest(client, message); } else if(message.type == "COLLABROOM") { if (thisSession.readonly) { messageLogger.warn("Dropped message, COLLABROOM for readonly pad"); } else if (message.data.type == "USER_CHANGES") { stats.counter('pendingEdits').inc() - padChannels.emit(message.padId, {client: client, message: message});// add to pad queue + padChannels.emit(message.padId, {client: client, message: message}); // add to pad queue } else if (message.data.type == "USERINFO_UPDATE") { handleUserInfoUpdate(client, message); } else if (message.data.type == "CHAT_MESSAGE") { @@ -242,7 +240,7 @@ exports.handleMessage = function(client, message) } else { messageLogger.warn("Dropped message, unknown Message Type " + message.type); } - }; + } /* * In a previous version of this code, an "if (message)" wrapped the @@ -259,11 +257,11 @@ exports.handleMessage = function(client, message) async.series([ handleMessageHook, - //check permissions - function(callback) - { + + // check permissions + function(callback) { // client tried to auth for the first time (first msg from the client) - if(message.type == "CLIENT_READY") { + if (message.type == "CLIENT_READY") { createSessionInfo(client, message); } @@ -274,31 +272,27 @@ exports.handleMessage = function(client, message) // FIXME: Allow to override readwrite access with readonly // Simulate using the load testing tool - if(!sessioninfos[client.id].auth){ + if (!sessioninfos[client.id].auth) { console.error("Auth was never applied to a session. If you are using the stress-test tool then restart Etherpad and the Stress test tool.") return; } var auth = sessioninfos[client.id].auth; - var checkAccessCallback = function(err, statusObject) - { - if(ERR(err, callback)) return; + var checkAccessCallback = function(err, statusObject) { + if (ERR(err, callback)) return; - //access was granted - if(statusObject.accessStatus == "grant") - { + if (statusObject.accessStatus == "grant") { + // access was granted callback(); - } - //no access, send the client a message that tell him why - else - { + } else { + // no access, send the client a message that tells him why client.json.send({accessStatus: statusObject.accessStatus}) } }; - //check if pad is requested via readOnly + // check if pad is requested via readOnly if (auth.padID.indexOf("r.") === 0) { - //Pad is readOnly, first get the real Pad ID + // Pad is readOnly, first get the real Pad ID readOnlyManager.getPadId(auth.padID, function(err, value) { ERR(err); securityManager.checkAccess(value, auth.sessionID, auth.token, auth.password, checkAccessCallback); @@ -321,40 +315,40 @@ function handleSaveRevisionMessage(client, message){ var padId = sessioninfos[client.id].padId; var userId = sessioninfos[client.id].author; - padManager.getPad(padId, function(err, pad) - { - if(ERR(err)) return; + padManager.getPad(padId, function(err, pad) { + if (ERR(err)) return; pad.addSavedRevision(pad.head, userId); }); } /** - * Handles a custom message, different to the function below as it handles objects not strings and you can - * direct the message to specific sessionID + * Handles a custom message, different to the function below as it handles + * objects not strings and you can direct the message to specific sessionID * * @param msg {Object} the message we're sending * @param sessionID {string} the socketIO session to which we're sending this message */ -exports.handleCustomObjectMessage = function (msg, sessionID, cb) { - if(msg.data.type === "CUSTOM"){ - if(sessionID){ // If a sessionID is targeted then send directly to this sessionID - socketio.sockets.socket(sessionID).json.send(msg); // send a targeted message - }else{ - socketio.sockets.in(msg.data.payload.padId).json.send(msg); // broadcast to all clients on this pad +exports.handleCustomObjectMessage = function(msg, sessionID, cb) { + if (msg.data.type === "CUSTOM") { + if (sessionID){ + // a sessionID is targeted: directly to this sessionID + socketio.sockets.socket(sessionID).json.send(msg); + } else { + // broadcast to all clients on this pad + socketio.sockets.in(msg.data.payload.padId).json.send(msg); } } cb(null, {}); } - /** * Handles a custom message (sent via HTTP API request) * * @param padID {Pad} the pad to which we're sending this message * @param msgString {String} the message we're sending */ -exports.handleCustomMessage = function (padID, msgString, cb) { +exports.handleCustomMessage = function(padID, msgString, cb) { var time = Date.now(); var msg = { type: 'COLLABROOM', @@ -390,34 +384,33 @@ function handleChatMessage(client, message) * @param text the text of the chat message * @param padId the padId to send the chat message to */ -exports.sendChatMessageToPadClients = function (time, userId, text, padId) { +exports.sendChatMessageToPadClients = function(time, userId, text, padId) { var pad; var userName; async.series([ - //get the pad - function(callback) - { - padManager.getPad(padId, function(err, _pad) - { - if(ERR(err, callback)) return; + // get the pad + function(callback) { + padManager.getPad(padId, function(err, _pad) { + if (ERR(err, callback)) return; + pad = _pad; callback(); }); }, - function(callback) - { - authorManager.getAuthorName(userId, function(err, _userName) - { - if(ERR(err, callback)) return; + + function(callback) { + authorManager.getAuthorName(userId, function(err, _userName) { + if (ERR(err, callback)) return; + userName = _userName; callback(); }); }, - //save the chat message and broadcast it - function(callback) - { - //save the chat message + + // save the chat message and broadcast it + function(callback) { + // save the chat message pad.appendChatMessage(text, userId, time); var msg = { @@ -431,13 +424,13 @@ exports.sendChatMessageToPadClients = function (time, userId, text, padId) { } }; - //broadcast the chat message to everyone on the pad + // broadcast the chat message to everyone on the pad socketio.sockets.in(padId).json.send(msg); callback(); } - ], function(err) - { + ], + function(err) { ERR(err); }); } @@ -449,13 +442,12 @@ exports.sendChatMessageToPadClients = function (time, userId, text, padId) { */ function handleGetChatMessages(client, message) { - if(message.data.start == null) - { + if (message.data.start == null) { messageLogger.warn("Dropped message, GetChatMessages Message has no start!"); return; } - if(message.data.end == null) - { + + if (message.data.end == null) { messageLogger.warn("Dropped message, GetChatMessages Message has no start!"); return; } @@ -464,8 +456,7 @@ function handleGetChatMessages(client, message) var end = message.data.end; var count = end - start; - if(count < 0 || count > 100) - { + if (count < 0 || count > 100) { messageLogger.warn("Dropped message, GetChatMessages Message, client requested invalid amout of messages!"); return; } @@ -474,21 +465,19 @@ function handleGetChatMessages(client, message) var pad; async.series([ - //get the pad - function(callback) - { - padManager.getPad(padId, function(err, _pad) - { - if(ERR(err, callback)) return; + // get the pad + function(callback) { + padManager.getPad(padId, function(err, _pad) { + if (ERR(err, callback)) return; + pad = _pad; callback(); }); }, - function(callback) - { - pad.getChatMessages(start, end, function(err, chatMessages) - { - if(ERR(err, callback)) return; + + function(callback) { + pad.getChatMessages(start, end, function(err, chatMessages) { + if (ERR(err, callback)) return; var infoMsg = { type: "COLLABROOM", @@ -511,14 +500,13 @@ function handleGetChatMessages(client, message) */ function handleSuggestUserName(client, message) { - //check if all ok - if(message.data.payload.newName == null) - { + // check if all ok + if (message.data.payload.newName == null) { messageLogger.warn("Dropped message, suggestUserName Message has no newName!"); return; } - if(message.data.payload.unnamedId == null) - { + + if (message.data.payload.unnamedId == null) { messageLogger.warn("Dropped message, suggestUserName Message has no unnamedId!"); return; } @@ -526,10 +514,10 @@ function handleSuggestUserName(client, message) var padId = sessioninfos[client.id].padId; var roomClients = _getRoomClients(padId); - //search the author and send him this message + // search the author and send him this message roomClients.forEach(function(client) { var session = sessioninfos[client.id]; - if(session && session.author == message.data.payload.unnamedId) { + if (session && session.author == message.data.payload.unnamedId) { client.json.send(message); } }); @@ -542,37 +530,35 @@ function handleSuggestUserName(client, message) */ function handleUserInfoUpdate(client, message) { - //check if all ok - if(message.data.userInfo == null) - { + // check if all ok + if (message.data.userInfo == null) { messageLogger.warn("Dropped message, USERINFO_UPDATE Message has no userInfo!"); return; } - if(message.data.userInfo.colorId == null) - { + + if (message.data.userInfo.colorId == null) { messageLogger.warn("Dropped message, USERINFO_UPDATE Message has no colorId!"); return; } // Check that we have a valid session and author to update. var session = sessioninfos[client.id]; - if(!session || !session.author || !session.padId) - { + if (!session || !session.author || !session.padId) { messageLogger.warn("Dropped message, USERINFO_UPDATE Session not ready." + message.data); return; } - //Find out the author name of this session + // Find out the author name of this session var author = session.author; // Check colorId is a Hex color var isColor = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(message.data.userInfo.colorId) // for #f00 (Thanks Smamatti) - if(!isColor){ + if (!isColor) { messageLogger.warn("Dropped message, USERINFO_UPDATE Color is malformed." + message.data); return; } - //Tell the authorManager about the new attributes + // Tell the authorManager about the new attributes authorManager.setAuthorColorId(author, message.data.userInfo.colorId); authorManager.setAuthorName(author, message.data.userInfo.name); @@ -585,7 +571,7 @@ function handleUserInfoUpdate(client, message) type: "USER_NEWINFO", userInfo: { userId: author, - //set a null name, when there is no name set. cause the client wants it null + // set a null name, when there is no name set. cause the client wants it null name: message.data.userInfo.name || null, colorId: message.data.userInfo.colorId, userAgent: "Anonymous", @@ -594,7 +580,7 @@ function handleUserInfoUpdate(client, message) } }; - //Send the other clients on the pad the update message + // Send the other clients on the pad the update message client.broadcast.to(padId).json.send(infoMsg); } @@ -621,34 +607,34 @@ function handleUserChanges(data, cb) stats.counter('pendingEdits').dec() // Make sure all required fields are present - if(message.data.baseRev == null) - { + if (message.data.baseRev == null) { messageLogger.warn("Dropped message, USER_CHANGES Message has no baseRev!"); return cb(); } - if(message.data.apool == null) - { + + if (message.data.apool == null) { messageLogger.warn("Dropped message, USER_CHANGES Message has no apool!"); return cb(); } - if(message.data.changeset == null) - { + + if (message.data.changeset == null) { messageLogger.warn("Dropped message, USER_CHANGES Message has no changeset!"); return cb(); } - //TODO: this might happen with other messages too => find one place to copy the session - //and always use the copy. atm a message will be ignored if the session is gone even - //if the session was valid when the message arrived in the first place - if(!sessioninfos[client.id]) - { + + // TODO: this might happen with other messages too => find one place to copy the session + // and always use the copy. atm a message will be ignored if the session is gone even + // if the session was valid when the message arrived in the first place + if (!sessioninfos[client.id]) { messageLogger.warn("Dropped message, disconnect happened in the mean time"); return cb(); } - //get all Vars we need + // get all Vars we need var baseRev = message.data.baseRev; var wireApool = (new AttributePool()).fromJsonable(message.data.apool); var changeset = message.data.changeset; + // The client might disconnect between our callbacks. We should still // finish processing the changeset, so keep a reference to the session. var thisSession = sessioninfos[client.id]; @@ -659,31 +645,29 @@ function handleUserChanges(data, cb) var stopWatch = stats.timer('edits').start(); async.series([ - //get the pad - function(callback) - { - padManager.getPad(thisSession.padId, function(err, value) - { - if(ERR(err, callback)) return; + // get the pad + function(callback) { + padManager.getPad(thisSession.padId, function(err, value) { + if (ERR(err, callback)) return; + pad = value; callback(); }); }, - //create the changeset - function(callback) - { - //ex. _checkChangesetAndPool - try - { + // create the changeset + function(callback) { + // ex. _checkChangesetAndPool + + try { // Verify that the changeset has valid syntax and is in canonical form Changeset.checkRep(changeset); // Verify that the attribute indexes used in the changeset are all // defined in the accompanying attribute pool. Changeset.eachAttribNumber(changeset, function(n) { - if (! wireApool.getAttrib(n)) { - throw new Error("Attribute pool is missing attribute "+n+" for changeset "+changeset); + if (!wireApool.getAttrib(n)) { + throw new Error("Attribute pool is missing attribute " + n + " for changeset " + changeset); } }); @@ -693,102 +677,105 @@ function handleUserChanges(data, cb) while(iterator.hasNext()) { op = iterator.next() - //+ can add text with attribs - //= can change or add attribs - //- can have attribs, but they are discarded and don't show up in the attribs - but do show up in the pool + // + can add text with attribs + // = can change or add attribs + // - can have attribs, but they are discarded and don't show up in the attribs - but do show up in the pool op.attribs.split('*').forEach(function(attr) { - if(!attr) return + if (!attr) return; + attr = wireApool.getAttrib(attr) - if(!attr) return - //the empty author is used in the clearAuthorship functionality so this should be the only exception - if('author' == attr[0] && (attr[1] != thisSession.author && attr[1] != '')) throw new Error("Trying to submit changes as another author in changeset "+changeset); - }) + if (!attr) return; + + // the empty author is used in the clearAuthorship functionality so this should be the only exception + if ('author' == attr[0] && (attr[1] != thisSession.author && attr[1] != '')) { + throw new Error("Trying to submit changes as another author in changeset " + changeset); + } + }); } - //ex. adoptChangesetAttribs + // ex. adoptChangesetAttribs - //Afaik, it copies the new attributes from the changeset, to the global Attribute Pool + // Afaik, it copies the new attributes from the changeset, to the global Attribute Pool changeset = Changeset.moveOpsToNewPool(changeset, wireApool, pad.pool); - } - catch(e) - { + } catch(e) { // There is an error in this changeset, so just refuse it client.json.send({disconnect:"badChangeset"}); stats.meter('failedChangesets').mark(); return callback(new Error("Can't apply USER_CHANGES, because "+e.message)); } - //ex. applyUserChanges + // ex. applyUserChanges apool = pad.pool; r = baseRev; // The client's changeset might not be based on the latest revision, // since other clients are sending changes at the same time. // Update the changeset so that it can be applied to the latest revision. - //https://github.com/caolan/async#whilst + // https://github.com/caolan/async#whilst async.whilst( function() { return r < pad.getHeadRevisionNumber(); }, function(callback) { r++; - pad.getRevisionChangeset(r, function(err, c) - { - if(ERR(err, callback)) return; + pad.getRevisionChangeset(r, function(err, c) { + if (ERR(err, callback)) return; // At this point, both "c" (from the pad) and "changeset" (from the // client) are relative to revision r - 1. The follow function // rebases "changeset" so that it is relative to revision r // and can be applied after "c". - try - { + try { // a changeset can be based on an old revision with the same changes in it // prevent eplite from accepting it TODO: better send the client a NEW_CHANGES // of that revision - if(baseRev+1 == r && c == changeset) { + if (baseRev + 1 == r && c == changeset) { client.json.send({disconnect:"badChangeset"}); stats.meter('failedChangesets').mark(); + return callback(new Error("Won't apply USER_CHANGES, because it contains an already accepted changeset")); } + changeset = Changeset.follow(c, changeset, false, apool); - }catch(e){ + } catch(e) { client.json.send({disconnect:"badChangeset"}); stats.meter('failedChangesets').mark(); + return callback(new Error("Can't apply USER_CHANGES, because "+e.message)); } - if ((r - baseRev) % 200 == 0) { // don't let the stack get too deep + if ((r - baseRev) % 200 == 0) { + // don't let the stack get too deep async.nextTick(callback); } else { callback(null); } }); }, - //use the callback of the series function + + // use the callback of the series function callback ); }, - //do correction changesets, and send it to all users - function (callback) - { + + // do correction changesets, and send it to all users + function(callback) { var prevText = pad.text(); - if (Changeset.oldLen(changeset) != prevText.length) - { + if (Changeset.oldLen(changeset) != prevText.length) { client.json.send({disconnect:"badChangeset"}); stats.meter('failedChangesets').mark(); + return callback(new Error("Can't apply USER_CHANGES "+changeset+" with oldLen " + Changeset.oldLen(changeset) + " to document of length " + prevText.length)); } - try - { + try { pad.appendRevision(changeset, thisSession.author); - } - catch(e) - { + } catch(e) { client.json.send({disconnect:"badChangeset"}); stats.meter('failedChangesets').mark(); + return callback(e) } @@ -809,21 +796,25 @@ function handleUserChanges(data, cb) }); callback(); } - ], function(err) - { + ], + function(err) { stopWatch.end() cb(); - if(err) console.warn(err.stack || err) + + if(err) { + console.warn(err.stack || err); + } }); } exports.updatePadClients = function(pad, callback) { - //skip this step if noone is on this pad + // skip this if no-one is on this pad var roomClients = _getRoomClients(pad.id); - if(roomClients.length==0) + if (roomClients.length == 0) { return callback(); + } // since all clients usually get the same set of changesets, store them in local cache // to remove unnecessary roundtrip to the datalayer @@ -833,26 +824,27 @@ exports.updatePadClients = function(pad, callback) // but benefit of reusing cached revision object is HUGE var revCache = {}; - //go trough all sessions on this pad + // go through all sessions on this pad async.forEach(roomClients, function(client, callback){ var sid = client.id; - //https://github.com/caolan/async#whilst - //send them all new changesets + // https://github.com/caolan/async#whilst + // send them all new changesets async.whilst( - function (){ return sessioninfos[sid] && sessioninfos[sid].rev < pad.getHeadRevisionNumber()}, + function() { return sessioninfos[sid] && sessioninfos[sid].rev < pad.getHeadRevisionNumber()}, function(callback) { var r = sessioninfos[sid].rev + 1; async.waterfall([ function(callback) { - if(revCache[r]) + if(revCache[r]) { callback(null, revCache[r]); - else + } else { pad.getRevision(r, callback); + } }, - function(revision, callback) - { + + function(revision, callback) { revCache[r] = revision; var author = revision.meta.author, @@ -860,15 +852,13 @@ exports.updatePadClients = function(pad, callback) currentTime = revision.meta.timestamp; // next if session has not been deleted - if(sessioninfos[sid] == null) + if (sessioninfos[sid] == null) { return callback(null); - - if(author == sessioninfos[sid].author) - { - client.json.send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}}); } - else - { + + if (author == sessioninfos[sid].author) { + client.json.send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}}); + } else { var forWire = Changeset.prepareForWire(revChangeset, pad.pool); var wireMsg = {"type":"COLLABROOM", "data":{type:"NEW_CHANGES", @@ -882,7 +872,8 @@ exports.updatePadClients = function(pad, callback) client.json.send(wireMsg); } - if(sessioninfos[sid]){ + + if (sessioninfos[sid]) { sessioninfos[sid].time = currentTime; sessioninfos[sid].rev = r; } @@ -909,19 +900,18 @@ function _correctMarkersInPad(atext, apool) { while (iter.hasNext()) { var op = iter.next(); - var hasMarker = _.find(AttributeManager.lineAttributes, function(attribute){ + var hasMarker = _.find(AttributeManager.lineAttributes, function(attribute) { return Changeset.opAttributeValue(op, attribute, apool); }) !== undefined; if (hasMarker) { - for(var i=0;i 0 && text.charAt(offset-1) != '\n') { badMarkers.push(offset); } offset++; } - } - else { + } else { offset += op.chars; } } @@ -932,12 +922,15 @@ function _correctMarkersInPad(atext, apool) { // create changeset that removes these bad markers offset = 0; + var builder = Changeset.builder(text.length); + badMarkers.forEach(function(pos) { builder.keepText(text.substring(offset, pos)); builder.remove(1); offset = pos+1; }); + return builder.toString(); } @@ -950,7 +943,7 @@ function handleSwitchToPad(client, message) async.forEach(roomClients, function(client, callback) { var sinfo = sessioninfos[client.id]; - if(sinfo && sinfo.author == currentSession.author) { + if (sinfo && sinfo.author == currentSession.author) { // fix user's counter, works on page refresh or if user closes browser window and then rejoins sessioninfos[client.id] = {}; client.leave(padId); @@ -986,24 +979,23 @@ function createSessionInfo(client, message) */ function handleClientReady(client, message) { - //check if all ok - if(!message.token) - { + // check if all ok + if (!message.token) { messageLogger.warn("Dropped message, CLIENT_READY Message has no token!"); return; } - if(!message.padId) - { + + if (!message.padId) { messageLogger.warn("Dropped message, CLIENT_READY Message has no padId!"); return; } - if(!message.protocolVersion) - { + + if (!message.protocolVersion) { messageLogger.warn("Dropped message, CLIENT_READY Message has no protocolVersion!"); return; } - if(message.protocolVersion != 2) - { + + if (message.protocolVersion != 2) { messageLogger.warn("Dropped message, CLIENT_READY Message has a unknown protocolVersion '" + message.protocolVersion + "'!"); return; } @@ -1019,97 +1011,92 @@ function handleClientReady(client, message) hooks.callAll("clientReady", message); async.series([ - //Get ro/rw id:s - function (callback) - { + // Get ro/rw id:s + function(callback) { readOnlyManager.getIds(message.padId, function(err, value) { - if(ERR(err, callback)) return; + if (ERR(err, callback)) return; + padIds = value; callback(); }); }, - //check permissions - function(callback) - { + + // check permissions + function(callback) { // Note: message.sessionID is an entierly different kind of - // session from the sessions we use here! Beware! FIXME: Call - // our "sessions" "connections". + // session from the sessions we use here! Beware! + // FIXME: Call our "sessions" "connections". // FIXME: Use a hook instead // FIXME: Allow to override readwrite access with readonly - securityManager.checkAccess (padIds.padId, message.sessionID, message.token, message.password, function(err, statusObject) - { - if(ERR(err, callback)) return; + securityManager.checkAccess(padIds.padId, message.sessionID, message.token, message.password, function(err, statusObject) { + if (ERR(err, callback)) return; - //access was granted - if(statusObject.accessStatus == "grant") - { + if (statusObject.accessStatus == "grant") { + // access was granted author = statusObject.authorID; callback(); - } - //no access, send the client a message that tell him why - else - { + } else { + // no access, send the client a message that tells him why client.json.send({accessStatus: statusObject.accessStatus}) } }); }, - //get all authordata of this new user, and load the pad-object from the database + + // get all authordata of this new user, and load the pad-object from the database function(callback) { async.parallel([ - //get colorId and name - function(callback) - { - authorManager.getAuthor(author, function(err, value) - { - if(ERR(err, callback)) return; + // get colorId and name + function(callback) { + authorManager.getAuthor(author, function(err, value) { + if (ERR(err, callback)) return; + authorColorId = value.colorId; authorName = value.name; callback(); }); }, - //get pad - function(callback) - { - padManager.getPad(padIds.padId, function(err, value) - { - if(ERR(err, callback)) return; + + // get pad + function(callback) { + padManager.getPad(padIds.padId, function(err, value) { + if (ERR(err, callback)) return; + pad = value; callback(); }); } ], callback); }, - //these db requests all need the pad object (timestamp of latest revission, author data) - function(callback) - { + + // these db requests all need the pad object (timestamp of latest revission, author data) + function(callback) { var authors = pad.getAllAuthors(); async.parallel([ - //get timestamp of latest revission needed for timeslider - function(callback) - { - pad.getRevisionDate(pad.getHeadRevisionNumber(), function(err, date) - { - if(ERR(err, callback)) return; + // get timestamp of latest revission needed for timeslider + function(callback) { + pad.getRevisionDate(pad.getHeadRevisionNumber(), function(err, date) { + if (ERR(err, callback)) return; + currentTime = date; callback(); }); }, - //get all author data out of the database - function(callback) - { - async.forEach(authors, function(authorId, callback) - { - authorManager.getAuthor(authorId, function(err, author) - { - if(!author && !err) - { + + // get all author data out of the database + function(callback) { + async.forEach(authors, function(authorId, callback) { + authorManager.getAuthor(authorId, function(err, author) { + if (!author && !err) { messageLogger.error("There is no author for authorId:", authorId); + return callback(); } - if(ERR(err, callback)) return; - historicalAuthorData[authorId] = {name: author.name, colorId: author.colorId}; // Filter author attribs (e.g. don't send author's pads to all clients) + + if (ERR(err, callback)) return; + + historicalAuthorData[authorId] = { name: author.name, colorId: author.colorId }; // Filter author attribs (e.g. don't send author's pads to all clients) callback(); }); }, callback); @@ -1117,56 +1104,57 @@ function handleClientReady(client, message) ], callback); }, - //glue the clientVars together, send them and tell the other clients that a new one is there - function(callback) - { - //Check that the client is still here. It might have disconnected between callbacks. - if(sessioninfos[client.id] === undefined) - return callback(); - //Check if this author is already on the pad, if yes, kick the other sessions! + // glue the clientVars together, send them and tell the other clients that a new one is there + function(callback) { + // Check that the client is still here. It might have disconnected between callbacks. + if(sessioninfos[client.id] === undefined) { + return callback(); + } + + // Check if this author is already on the pad, if yes, kick the other sessions! var roomClients = _getRoomClients(pad.id); async.forEach(roomClients, function(client, callback) { var sinfo = sessioninfos[client.id]; - if(sinfo && sinfo.author == author) { + + if (sinfo && sinfo.author == author) { // fix user's counter, works on page refresh or if user closes browser window and then rejoins sessioninfos[client.id] = {}; client.leave(padIds.padId); - client.json.send({disconnect:"userdup"}); + client.json.send({ disconnect:"userdup" }); } }); - //Save in sessioninfos that this session belonges to this pad + // Save in sessioninfos that this session belonges to this pad sessioninfos[client.id].padId = padIds.padId; sessioninfos[client.id].readOnlyPadId = padIds.readOnlyPadId; sessioninfos[client.id].readonly = padIds.readonly; - //Log creation/(re-)entering of a pad + // Log creation/(re-)entering of a pad var ip = remoteAddress[client.id]; - //Anonymize the IP address if IP logging is disabled - if(settings.disableIPlogging) { + // Anonymize the IP address if IP logging is disabled + if (settings.disableIPlogging) { ip = 'ANONYMOUS'; } - if(pad.head > 0) { - accessLogger.info('[ENTER] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" entered the pad'); - } - else if(pad.head == 0) { - accessLogger.info('[CREATE] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" created the pad'); + if (pad.head > 0) { + accessLogger.info('[ENTER] Pad "' + padIds.padId + '": Client ' + client.id + ' with IP "' + ip + '" entered the pad'); + } else if (pad.head == 0) { + accessLogger.info('[CREATE] Pad "' + padIds.padId + '": Client ' + client.id + ' with IP "' + ip + '" created the pad'); } - //If this is a reconnect, we don't have to send the client the ClientVars again - if(message.reconnect == true) - { - //Join the pad and start receiving updates + if (message.reconnect == true) { + // If this is a reconnect, we don't have to send the client the ClientVars again + // Join the pad and start receiving updates client.join(padIds.padId); - //Save the revision in sessioninfos, we take the revision from the info the client send to us + + // 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 + // 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 = {}; @@ -1174,68 +1162,68 @@ function handleClientReady(client, message) var endNum = pad.getHeadRevisionNumber() + 1; async.series([ - //push all the revision numbers needed into revisionsNeeded array - function(callback) - { + // 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;r headNum+1) { + endNum = headNum+1; + } + + if (startNum < 0) { + startNum = 0; + } + + for (var r = startNum; r < endNum; r++) { revisionsNeeded.push(r); changesets[r] = {}; } + callback(); }, - //get changesets needed for pending revisions - function(callback) - { - async.eachSeries(revisionsNeeded, function(revNum, callback) - { - pad.getRevisionChangeset(revNum, function(err, value) - { - if(ERR(err)) return; + + // get changesets needed for pending revisions + function(callback) { + async.eachSeries(revisionsNeeded, function(revNum, callback) { + pad.getRevisionChangeset(revNum, function(err, value) { + if (ERR(err)) return; + changesets[revNum]['changeset'] = value; callback(); }); }, callback); }, - //get author for each changeset - function(callback) - { - async.eachSeries(revisionsNeeded, function(revNum, callback) - { - pad.getRevisionAuthor(revNum, function(err, value) - { - if(ERR(err)) return; + + // get author for each changeset + function(callback) { + async.eachSeries(revisionsNeeded, function(revNum, callback) { + pad.getRevisionAuthor(revNum, function(err, value) { + if (ERR(err)) return; + changesets[revNum]['author'] = value; callback(); }); }, callback); }, - //get timestamp for each changeset - function(callback) - { - async.eachSeries(revisionsNeeded, function(revNum, callback) - { - pad.getRevisionDate(revNum, function(err, value) - { - if(ERR(err)) return; + + // get timestamp for each changeset + function(callback) { + async.eachSeries(revisionsNeeded, function(revNum, callback) { + pad.getRevisionDate(revNum, function(err, value) { + if (ERR(err)) return; + changesets[revNum]['timestamp'] = value; callback(); }); }, callback); } ], - //return error and pending changesets - function(err) - { - if(ERR(err, callback)) return; - async.eachSeries(revisionsNeeded, function(r, callback) - { + + // return error and pending changesets + function(err) { + if (ERR(err, callback)) return; + + async.eachSeries(revisionsNeeded, function(r, callback) { var forWire = Changeset.prepareForWire(changesets[r]['changeset'], pad.pool); var wireMsg = {"type":"COLLABROOM", "data":{type:"CLIENT_RECONNECT", @@ -1249,8 +1237,8 @@ function handleClientReady(client, message) client.json.send(wireMsg); callback(); }); - if (startNum == endNum) - { + + if (startNum == endNum) { var Msg = {"type":"COLLABROOM", "data":{type:"CLIENT_RECONNECT", noChanges: true, @@ -1259,19 +1247,17 @@ function handleClientReady(client, message) client.json.send(Msg); } }); - } - //This is a normal first connect - else - { - //prepare all values for the wire, there'S a chance that this throws, if the pad is corrupted + } else { + // This is a normal first connect + // prepare all values for the wire, there's a chance that this throws, if the pad is corrupted try { var atext = Changeset.cloneAText(pad.atext); var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool); var apool = attribsForWire.pool.toJsonable(); atext.attribs = attribsForWire.translated; - }catch(e) { + } catch(e) { console.error(e.stack || e) - client.json.send({disconnect:"corruptPad"});// pull the breaks + client.json.send({ disconnect:"corruptPad" });// pull the brakes return callback(); } @@ -1335,35 +1321,36 @@ function handleClientReady(client, message) "initialChangesets": [] // FIXME: REMOVE THIS SHIT } - //Add a username to the clientVars if one avaiable - if(authorName != null) - { + // Add a username to the clientVars if one avaiable + if (authorName != null) { clientVars.userName = authorName; } - //call the clientVars-hook so plugins can modify them before they get sent to the client - hooks.aCallAll("clientVars", { clientVars: clientVars, pad: pad }, function ( err, messages ) { - if(ERR(err, callback)) return; + // call the clientVars-hook so plugins can modify them before they get sent to the client + hooks.aCallAll("clientVars", { clientVars: clientVars, pad: pad }, function( err, messages ) { + if (ERR(err, callback)) return; _.each(messages, function(newVars) { - //combine our old object with the new attributes from the hook + // combine our old object with the new attributes from the hook for(var attr in newVars) { clientVars[attr] = newVars[attr]; } }); - //Join the pad and start receiving updates + // Join the pad and start receiving updates client.join(padIds.padId); - //Send the clientVars to the Client - client.json.send({type: "CLIENT_VARS", data: clientVars}); - //Save the current revision in sessioninfos, should be the same as in clientVars + + // Send the clientVars to the Client + client.json.send({ type: "CLIENT_VARS", data: clientVars }); + + // Save the current revision in sessioninfos, should be the same as in clientVars sessioninfos[client.id].rev = pad.getHeadRevisionNumber(); }); } sessioninfos[client.id].author = author; - //prepare the notification for the other users on the pad, that this user joined + // prepare the notification for the other users on the pad, that this user joined var messageToTheOtherUsers = { "type": "COLLABROOM", "data": { @@ -1377,47 +1364,47 @@ function handleClientReady(client, message) } }; - //Add the authorname of this new User, if avaiable - if(authorName != null) - { + // Add the authorname of this new User, if avaiable + if (authorName != null) { messageToTheOtherUsers.data.userInfo.name = authorName; } // notify all existing users about new user client.broadcast.to(padIds.padId).json.send(messageToTheOtherUsers); - //Get sessions for this pad + // Get sessions for this pad var roomClients = _getRoomClients(pad.id); - async.forEach(roomClients, function(roomClient, callback) - { + async.forEach(roomClients, function(roomClient, callback) { var author; - //Jump over, if this session is the connection session - if(roomClient.id == client.id) + // Jump over, if this session is the connection session + if (roomClient.id == client.id) { return callback(); + } - - //Since sessioninfos might change while being enumerated, check if the - //sessionID is still assigned to a valid session - if(sessioninfos[roomClient.id] !== undefined) + // Since sessioninfos might change while being enumerated, check if the + // sessionID is still assigned to a valid session + if (sessioninfos[roomClient.id] !== undefined) { author = sessioninfos[roomClient.id].author; - else // If the client id is not valid, callback(); + } else { + // If the client id is not valid, callback(); return callback(); + } async.waterfall([ - //get the authorname & colorId - function(callback) - { + // get the authorname & colorId + function(callback) { // reuse previously created cache of author's data - if(historicalAuthorData[author]) + if (historicalAuthorData[author]) { callback(null, historicalAuthorData[author]); - else + } else { authorManager.getAuthor(author, callback); + } }, - function (authorInfo, callback) - { - //Send the new User a Notification about this other user + + function(authorInfo, callback) { + // Send the new User a Notification about this other user var msg = { "type": "COLLABROOM", "data": { @@ -1431,13 +1418,14 @@ function handleClientReady(client, message) } } }; + client.json.send(msg); } ], callback); }, callback); } - ],function(err) - { + ], + function(err) { ERR(err); }); } @@ -1447,35 +1435,34 @@ function handleClientReady(client, message) */ function handleChangesetRequest(client, message) { - //check if all ok - if(message.data == null) - { + // check if all ok + if (message.data == null) { messageLogger.warn("Dropped message, changeset request has no data!"); return; } - if(message.padId == null) - { + + if (message.padId == null) { messageLogger.warn("Dropped message, changeset request has no padId!"); return; } - if(message.data.granularity == null) - { + + if (message.data.granularity == null) { messageLogger.warn("Dropped message, changeset request has no granularity!"); return; } - //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger#Polyfill - if(Math.floor(message.data.granularity) !== message.data.granularity) - { + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger#Polyfill + if (Math.floor(message.data.granularity) !== message.data.granularity) { messageLogger.warn("Dropped message, changeset request granularity is not an integer!"); return; } - if(message.data.start == null) - { + + if (message.data.start == null) { messageLogger.warn("Dropped message, changeset request has no start!"); return; } - if(message.data.requestID == null) - { + + if (message.data.requestID == null) { messageLogger.warn("Dropped message, changeset request has no requestID!"); return; } @@ -1486,23 +1473,24 @@ function handleChangesetRequest(client, message) var padIds; async.series([ - function (callback) { + function(callback) { readOnlyManager.getIds(message.padId, function(err, value) { - if(ERR(err, callback)) return; + if (ERR(err, callback)) return; + padIds = value; callback(); }); }, - function (callback) { - //build the requested rough changesets and send them back - getChangesetInfo(padIds.padId, start, end, granularity, function(err, changesetInfo) - { - if(err) return console.error('Error while handling a changeset request for '+padIds.padId, err, message.data); + + function(callback) { + // build the requested rough changesets and send them back + getChangesetInfo(padIds.padId, start, end, granularity, function(err, changesetInfo) { + if (err) return console.error('Error while handling a changeset request for ' + padIds.padId, err, message.data); var data = changesetInfo; data.requestID = message.data.requestID; - client.json.send({type: "CHANGESET_REQ", data: data}); + client.json.send({ type: "CHANGESET_REQ", data: data }); }); } ]); @@ -1526,96 +1514,91 @@ function getChangesetInfo(padId, startNum, endNum, granularity, callback) var head_revision = 0; async.series([ - //get the pad from the database - function(callback) - { - padManager.getPad(padId, function(err, _pad) - { - if(ERR(err, callback)) return; + // get the pad from the database + function(callback) { + padManager.getPad(padId, function(err, _pad) { + if (ERR(err, callback)) return; + pad = _pad; head_revision = pad.getHeadRevisionNumber(); callback(); }); }, - function(callback) - { - //calculate the last full endnum + + function(callback) { + // calculate the last full endnum var lastRev = pad.getHeadRevisionNumber(); - if (endNum > lastRev+1) { - endNum = lastRev+1; + if (endNum > lastRev + 1) { + endNum = lastRev + 1; } - endNum = Math.floor(endNum / granularity)*granularity; + + endNum = Math.floor(endNum / granularity) * granularity; var compositesChangesetNeeded = []; var revTimesNeeded = []; - //figure out which composite Changeset and revTimes we need, to load them in bulk + // figure out which composite Changeset and revTimes we need, to load them in bulk var compositeStart = startNum; - while (compositeStart < endNum) - { + while (compositeStart < endNum) { var compositeEnd = compositeStart + granularity; - //add the composite Changeset we needed - compositesChangesetNeeded.push({start: compositeStart, end: compositeEnd}); + // add the composite Changeset we needed + compositesChangesetNeeded.push({ start: compositeStart, end: compositeEnd }); - //add the t1 time we need + // add the t1 time we need revTimesNeeded.push(compositeStart == 0 ? 0 : compositeStart - 1); - //add the t2 time we need + + // add the t2 time we need revTimesNeeded.push(compositeEnd - 1); compositeStart += granularity; } - //get all needed db values parallel + // get all needed db values parallel async.parallel([ - function(callback) - { - //get all needed composite Changesets - async.forEach(compositesChangesetNeeded, function(item, callback) - { - composePadChangesets(padId, item.start, item.end, function(err, changeset) - { - if(ERR(err, callback)) return; + function(callback) { + // get all needed composite Changesets + async.forEach(compositesChangesetNeeded, function(item, callback) { + composePadChangesets(padId, item.start, item.end, function(err, changeset) { + if (ERR(err, callback)) return; + composedChangesets[item.start + "/" + item.end] = changeset; callback(); }); }, callback); }, - function(callback) - { - //get all needed revision Dates - async.forEach(revTimesNeeded, function(revNum, callback) - { - pad.getRevisionDate(revNum, function(err, revDate) - { - if(ERR(err, callback)) return; + + function(callback) { + // get all needed revision Dates + async.forEach(revTimesNeeded, function(revNum, callback) { + pad.getRevisionDate(revNum, function(err, revDate) { + if (ERR(err, callback)) return; + revisionDate[revNum] = Math.floor(revDate/1000); callback(); }); }, callback); }, - //get the lines - function(callback) - { - getPadLines(padId, startNum-1, function(err, _lines) - { - if(ERR(err, callback)) return; + + // get the lines + function(callback) { + getPadLines(padId, startNum-1, function(err, _lines) { + if (ERR(err, callback)) return; + lines = _lines; callback(); }); } ], callback); }, - //doesn't know what happens here excatly :/ - function(callback) - { + + // don't know what happens here exactly :/ + function(callback) { var compositeStart = startNum; - while (compositeStart < endNum) - { + while (compositeStart < endNum) { var compositeEnd = compositeStart + granularity; - if (compositeEnd > endNum || compositeEnd > head_revision+1) - { + if (compositeEnd > endNum || compositeEnd > head_revision+1) { break; } @@ -1629,12 +1612,9 @@ function getChangesetInfo(padId, startNum, endNum, granularity, callback) var backwards2 = Changeset.moveOpsToNewPool(backwards, pad.apool(), apool); var t1, t2; - if (compositeStart == 0) - { + if (compositeStart == 0) { t1 = revisionDate[0]; - } - else - { + } else { t1 = revisionDate[compositeStart - 1]; } @@ -1649,9 +1629,9 @@ function getChangesetInfo(padId, startNum, endNum, granularity, callback) callback(); } - ], function(err) - { - if(ERR(err, callback)) return; + ], + function(err) { + if (ERR(err, callback)) return; callback(null, {forwardsChangesets: forwardsChangesets, backwardsChangesets: backwardsChangesets, @@ -1674,43 +1654,41 @@ function getPadLines(padId, revNum, callback) var pad; async.series([ - //get the pad from the database - function(callback) - { - padManager.getPad(padId, function(err, _pad) - { - if(ERR(err, callback)) return; + // get the pad from the database + function(callback) { + padManager.getPad(padId, function(err, _pad) { + if (ERR(err, callback)) return; + pad = _pad; callback(); }); }, - //get the atext - function(callback) - { - if(revNum >= 0) - { - pad.getInternalRevisionAText(revNum, function(err, _atext) - { - if(ERR(err, callback)) return; + + // get the atext + function(callback) { + if (revNum >= 0) { + pad.getInternalRevisionAText(revNum, function(err, _atext) { + if (ERR(err, callback)) return; + atext = _atext; callback(); }); - } - else - { + } else { atext = Changeset.makeAText("\n"); callback(null); } }, - function(callback) - { + + function(callback) { result.textlines = Changeset.splitTextLines(atext.text); result.alines = Changeset.splitAttributionLines(atext.attribs, atext.text); callback(null); } - ], function(err) - { - if(ERR(err, callback)) return; + ], + + function(err) { + if (ERR(err, callback)) return; + callback(null, result); }); } @@ -1726,74 +1704,77 @@ function composePadChangesets(padId, startNum, endNum, callback) var changeset; async.series([ - //get the pad from the database - function(callback) - { - padManager.getPad(padId, function(err, _pad) - { - if(ERR(err, callback)) return; + // get the pad from the database + function(callback) { + padManager.getPad(padId, function(err, _pad) { + if (ERR(err, callback)) return; + pad = _pad; callback(); }); }, - //fetch all changesets we need - function(callback) - { + + // fetch all changesets we need + function(callback) { var changesetsNeeded=[]; var headNum = pad.getHeadRevisionNumber(); - if (endNum > headNum+1) - endNum = headNum+1; - if (startNum < 0) + if (endNum > headNum + 1) { + endNum = headNum + 1; + } + + if (startNum < 0) { startNum = 0; - //create a array for all changesets, we will - //replace the values with the changeset later - for(var r=startNum;r b[property]) - return dir? 1 : -1; + if (a[property] < b[property]) { + return dir? -1 : 1; + } + + if (a[property] > b[property]) { + return dir? 1 : -1; + } + // a must be equal to b return 0; - }) + }); } diff --git a/src/node/hooks/express/errorhandling.js b/src/node/hooks/express/errorhandling.js index 104a9c1bb..9a07dc669 100644 --- a/src/node/hooks/express/errorhandling.js +++ b/src/node/hooks/express/errorhandling.js @@ -11,20 +11,23 @@ exports.gracefulShutdown = function(err) { console.error(err); } - //ensure there is only one graceful shutdown running - if(exports.onShutdown) return; + // ensure there is only one graceful shutdown running + if (exports.onShutdown) { + return; + } + exports.onShutdown = true; console.log("graceful shutdown..."); - //do the db shutdown + // do the db shutdown db.db.doShutdown(function() { console.log("db sucessfully closed."); process.exit(0); }); - setTimeout(function(){ + setTimeout(function() { process.exit(1); }, 3000); } @@ -35,14 +38,14 @@ exports.expressCreateServer = function (hook_name, args, cb) { exports.app = args.app; // Handle errors - args.app.use(function(err, req, res, next){ + args.app.use(function(err, req, res, next) { // if an error occurs Connect will pass it down // through these "error-handling" middleware // allowing you to respond however you like res.status(500).send({ error: 'Sorry, something bad happened!' }); console.error(err.stack? err.stack : err.toString()); stats.meter('http500').mark() - }) + }); /* * Connect graceful shutdown with sigint and uncaught exception diff --git a/src/node/hooks/express/importexport.js b/src/node/hooks/express/importexport.js index a62942cc0..fe97aa2f9 100644 --- a/src/node/hooks/express/importexport.js +++ b/src/node/hooks/express/importexport.js @@ -13,7 +13,7 @@ exports.expressCreateServer = function (hook_name, args, cb) { return; } - //if abiword is disabled, and this is a format we only support with abiword, output a message + // if abiword is disabled, and this is a format we only support with abiword, output a message if (settings.exportAvailable() == "no" && ["odt", "pdf", "doc"].indexOf(req.params.type) !== -1) { res.send("This export is not enabled at this Etherpad instance. Set the path to Abiword or SOffice in settings.json to enable this feature"); @@ -24,9 +24,8 @@ exports.expressCreateServer = function (hook_name, args, cb) { hasPadAccess(req, res, function() { console.log('req.params.pad', req.params.pad); - padManager.doesPadExists(req.params.pad, function(err, exists) - { - if(!exists) { + padManager.doesPadExists(req.params.pad, function(err, exists) { + if (!exists) { return next(); } @@ -35,12 +34,11 @@ exports.expressCreateServer = function (hook_name, args, cb) { }); }); - //handle import requests + // handle import requests args.app.post('/p/:pad/import', function(req, res, next) { hasPadAccess(req, res, function() { - padManager.doesPadExists(req.params.pad, function(err, exists) - { - if(!exists) { + padManager.doesPadExists(req.params.pad, function(err, exists) { + if (!exists) { return next(); } diff --git a/src/node/hooks/express/padreadonly.js b/src/node/hooks/express/padreadonly.js index bff8adf7b..77b197c7c 100644 --- a/src/node/hooks/express/padreadonly.js +++ b/src/node/hooks/express/padreadonly.js @@ -5,52 +5,45 @@ var hasPadAccess = require("../../padaccess"); var exporthtml = require("../../utils/ExportHtml"); exports.expressCreateServer = function (hook_name, args, cb) { - //serve read only pad - args.app.get('/ro/:id', function(req, res) - { + // serve read only pad + args.app.get('/ro/:id', function(req, res) { var html; var padId; async.series([ - //translate the read only pad to a padId - function(callback) - { - readOnlyManager.getPadId(req.params.id, function(err, _padId) - { + // translate the read only pad to a padId + function(callback) { + readOnlyManager.getPadId(req.params.id, function(err, _padId) { if(ERR(err, callback)) return; padId = _padId; - //we need that to tell hasPadAcess about the pad + // we need that to tell hasPadAcess about the pad req.params.pad = padId; callback(); }); }, - //render the html document - function(callback) - { - //return if the there is no padId - if(padId == null) - { + // render the html document + function(callback) { + // return if the there is no padId + if(padId == null) { callback("notfound"); return; } - hasPadAccess(req, res, function() - { - //render the html document - exporthtml.getPadHTMLDocument(padId, null, function(err, _html) - { + hasPadAccess(req, res, function() { + // render the html document + exporthtml.getPadHTMLDocument(padId, null, function(err, _html) { if(ERR(err, callback)) return; html = _html; callback(); }); }); } - ], function(err) - { - //throw any unexpected error + ], + function(err) { + // throw any unexpected error if(err && err != "notfound") ERR(err); diff --git a/src/node/hooks/express/padurlsanitize.js b/src/node/hooks/express/padurlsanitize.js index be3ffb1b4..a7fb9f33e 100644 --- a/src/node/hooks/express/padurlsanitize.js +++ b/src/node/hooks/express/padurlsanitize.js @@ -2,29 +2,26 @@ var padManager = require('../../db/PadManager'); var url = require('url'); exports.expressCreateServer = function (hook_name, args, cb) { - //redirects browser to the pad's sanitized url if needed. otherwise, renders the html + + // redirects browser to the pad's sanitized url if needed. otherwise, renders the html args.app.param('pad', function (req, res, next, padId) { - //ensure the padname is valid and the url doesn't end with a / - if(!padManager.isValidPadId(padId) || /\/$/.test(req.url)) - { + // ensure the padname is valid and the url doesn't end with a / + if (!padManager.isValidPadId(padId) || /\/$/.test(req.url)) { res.status(404).send('Such a padname is forbidden'); return; } padManager.sanitizePadId(padId, function(sanitizedPadId) { - //the pad id was sanitized, so we redirect to the sanitized version - if(sanitizedPadId != padId) - { + if (sanitizedPadId != padId) { + // the pad id was sanitized, so we redirect to the sanitized version var real_url = sanitizedPadId; real_url = encodeURIComponent(real_url); var query = url.parse(req.url).query; if ( query ) real_url += '?' + query; res.header('Location', real_url); res.status(302).send('You should be redirected to ' + real_url + ''); - } - //the pad id was fine, so just render it - else - { + } else { + // the pad id was fine, so just render it next(); } }); diff --git a/src/node/hooks/express/tests.js b/src/node/hooks/express/tests.js index d0dcc0cc6..b5c4ca56c 100644 --- a/src/node/hooks/express/tests.js +++ b/src/node/hooks/express/tests.js @@ -4,37 +4,35 @@ var path = require("path") , async = require("async"); exports.expressCreateServer = function (hook_name, args, cb) { - args.app.get('/tests/frontend/specs_list.js', function(req, res){ - + args.app.get('/tests/frontend/specs_list.js', function(req, res) { async.parallel({ - coreSpecs: function(callback){ + coreSpecs: function(callback) { exports.getCoreTests(callback); }, - pluginSpecs: function(callback){ + pluginSpecs: function(callback) { exports.getPluginTests(callback); } }, - function(err, results){ + function(err, results) { var files = results.coreSpecs; // push the core specs to a file object files = files.concat(results.pluginSpecs); // add the plugin Specs to the core specs console.debug("Sent browser the following test specs:", files.sort()); res.send("var specs_list = " + JSON.stringify(files.sort()) + ";\n"); }); - }); - // path.join seems to normalize by default, but we'll just be explicit var rootTestFolder = path.normalize(path.join(npm.root, "../tests/frontend/")); - var url2FilePath = function(url){ + var url2FilePath = function(url) { var subPath = url.substr("/tests/frontend".length); - if (subPath == ""){ + if (subPath == "") { subPath = "index.html" } subPath = subPath.split("?")[0]; var filePath = path.normalize(path.join(rootTestFolder, subPath)); + // make sure we jail the paths to the test folder, otherwise serve index if (filePath.indexOf(rootTestFolder) !== 0) { filePath = path.join(rootTestFolder, "index.html"); @@ -46,13 +44,13 @@ exports.expressCreateServer = function (hook_name, args, cb) { var specFilePath = url2FilePath(req.url); var specFileName = path.basename(specFilePath); - fs.readFile(specFilePath, function(err, content){ - if(err){ return res.send(500); } - + fs.readFile(specFilePath, function(err, content) { + if (err) { return res.send(500); } + content = "describe(" + JSON.stringify(specFileName) + ", function(){ " + content + " });"; res.send(content); - }); + }); }); args.app.get('/tests/frontend/*', function (req, res) { @@ -62,19 +60,21 @@ exports.expressCreateServer = function (hook_name, args, cb) { args.app.get('/tests/frontend', function (req, res) { res.redirect('/tests/frontend/'); - }); + }); } -exports.getPluginTests = function(callback){ +exports.getPluginTests = function(callback) { var pluginSpecs = []; var plugins = fs.readdirSync('node_modules'); - plugins.forEach(function(plugin){ - if(fs.existsSync("node_modules/"+plugin+"/static/tests/frontend/specs")){ // if plugins exists - var specFiles = fs.readdirSync("node_modules/"+plugin+"/static/tests/frontend/specs/"); - async.forEach(specFiles, function(spec){ // for each specFile push it to pluginSpecs - pluginSpecs.push("/static/plugins/"+plugin+"/static/tests/frontend/specs/" + spec); + plugins.forEach(function(plugin) { + if (fs.existsSync("node_modules/" + plugin + "/static/tests/frontend/specs")) { + // if plugins exists + var specFiles = fs.readdirSync("node_modules/" + plugin + "/static/tests/frontend/specs/"); + async.forEach(specFiles, function(spec) { + // for each specFile push it to pluginSpecs + pluginSpecs.push("/static/plugins/" + plugin + "/static/tests/frontend/specs/" + spec); }, - function(err){ + function(err) { // blow up if something bad happens! }); } @@ -82,10 +82,11 @@ exports.getPluginTests = function(callback){ callback(null, pluginSpecs); } -exports.getCoreTests = function(callback){ - fs.readdir('tests/frontend/specs', function(err, coreSpecs){ // get the core test specs - if(err){ return res.send(500); } +exports.getCoreTests = function(callback) { + // get the core test specs + fs.readdir('tests/frontend/specs', function(err, coreSpecs) { + if (err) { return res.send(500); } + callback(null, coreSpecs); }); } - diff --git a/src/node/padaccess.js b/src/node/padaccess.js index 1f2e8834b..c8c888cd0 100644 --- a/src/node/padaccess.js +++ b/src/node/padaccess.js @@ -1,16 +1,16 @@ var ERR = require("async-stacktrace"); var securityManager = require('./db/SecurityManager'); -//checks for padAccess +// checks for padAccess module.exports = function (req, res, callback) { securityManager.checkAccess(req.params.pad, req.cookies.sessionID, req.cookies.token, req.cookies.password, function(err, accessObj) { - if(ERR(err, callback)) return; + if (ERR(err, callback)) return; - //there is access, continue - if(accessObj.accessStatus == "grant") { + if (accessObj.accessStatus == "grant") { + // there is access, continue callback(); - //no access } else { + // no access res.status(403).send("403 - Can't touch this"); } }); diff --git a/src/node/server.js b/src/node/server.js index 3db54284c..2ba1fa47d 100755 --- a/src/node/server.js +++ b/src/node/server.js @@ -1,7 +1,7 @@ #!/usr/bin/env node /** - * This module is started with bin/run.sh. It sets up a Express HTTP and a Socket.IO Server. - * Static file Requests are answered directly from this module, Socket.IO messages are passed + * This module is started with bin/run.sh. It sets up a Express HTTP and a Socket.IO Server. + * Static file Requests are answered directly from this module, Socket.IO messages are passed * to MessageHandler and minfied requests are passed to minified. */ @@ -46,8 +46,8 @@ NodeVersion.enforceMinNodeVersion('8.9.0'); */ var stats = require('./stats'); stats.gauge('memoryUsage', function() { - return process.memoryUsage().rss -}) + return process.memoryUsage().rss; +}); var settings , db @@ -59,8 +59,8 @@ async.waterfall([ // load npm function(callback) { npm.load({}, function(er) { - callback(er) - }) + callback(er); + }); }, // load everything @@ -73,14 +73,13 @@ async.waterfall([ callback(); }, - //initalize the database - function (callback) - { + // initalize the database + function (callback) { db.init(callback); }, function(callback) { - plugins.update(callback) + plugins.update(callback); }, function (callback) { @@ -93,9 +92,8 @@ async.waterfall([ callback(); }, - //initalize the http server - function (callback) - { + // initalize the http server + function (callback) { hooks.callAll("createServer", {}); callback(null); } diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js index 8a40e800d..a42878df6 100644 --- a/src/node/utils/ExportTxt.js +++ b/src/node/utils/ExportTxt.js @@ -30,40 +30,32 @@ function getPadTXT(pad, revNum, callback) var atext = pad.atext; var html; async.waterfall([ + // fetch revision atext + function(callback) { + if (revNum != undefined) { + pad.getInternalRevisionAText(revNum, function(err, revisionAtext) { + if (ERR(err, callback)) return; - - function (callback) - { - if (revNum != undefined) - { - pad.getInternalRevisionAText(revNum, function (err, revisionAtext) - { - if(ERR(err, callback)) return; atext = revisionAtext; callback(); }); - } - else - { + } else { callback(null); } }, // convert atext to html - - - function (callback) - { - html = getTXTFromAtext(pad, atext); // only this line is different to the HTML function + function(callback) { + // only this line is different to the HTML function + html = getTXTFromAtext(pad, atext); callback(null); }], + // run final callback + function(err) { + if (ERR(err, callback)) return; - - function (err) - { - if(ERR(err, callback)) return; callback(null, html); }); } @@ -80,17 +72,14 @@ function getTXTFromAtext(pad, atext, authorColors) var anumMap = {}; var css = ""; - props.forEach(function (propName, i) - { + props.forEach(function(propName, i) { var propTrueNum = apool.putAttrib([propName, true], true); - if (propTrueNum >= 0) - { + if (propTrueNum >= 0) { anumMap[propTrueNum] = i; } }); - function getLineTXT(text, attribs) - { + function getLineTXT(text, attribs) { var propVals = [false, false, false]; var ENTER = 1; var STAY = 2; @@ -106,94 +95,77 @@ function getTXTFromAtext(pad, atext, authorColors) var idx = 0; - function processNextChars(numChars) - { - if (numChars <= 0) - { + function processNextChars(numChars) { + if (numChars <= 0) { return; } var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars)); idx += numChars; - while (iter.hasNext()) - { + while (iter.hasNext()) { var o = iter.next(); var propChanged = false; - Changeset.eachAttribNumber(o.attribs, function (a) - { - if (a in anumMap) - { + + Changeset.eachAttribNumber(o.attribs, function(a) { + if (a in anumMap) { var i = anumMap[a]; // i = 0 => bold, etc. - if (!propVals[i]) - { + + if (!propVals[i]) { propVals[i] = ENTER; propChanged = true; - } - else - { + } else { propVals[i] = STAY; } } }); - for (var i = 0; i < propVals.length; i++) - { - if (propVals[i] === true) - { + + for (var i = 0; i < propVals.length; i++) { + if (propVals[i] === true) { propVals[i] = LEAVE; propChanged = true; - } - else if (propVals[i] === STAY) - { - propVals[i] = true; // set it back + } else if (propVals[i] === STAY) { + // set it back + propVals[i] = true; } } + // now each member of propVal is in {false,LEAVE,ENTER,true} // according to what happens at start of span - if (propChanged) - { + if (propChanged) { // leaving bold (e.g.) also leaves italics, etc. var left = false; - for (var i = 0; i < propVals.length; i++) - { + + for (var i = 0; i < propVals.length; i++) { var v = propVals[i]; - if (!left) - { - if (v === LEAVE) - { + + if (!left) { + if (v === LEAVE) { left = true; } - } - else - { - if (v === true) - { - propVals[i] = STAY; // tag will be closed and re-opened + } else { + if (v === true) { + // tag will be closed and re-opened + propVals[i] = STAY; } } } var tags2close = []; - for (var i = propVals.length - 1; i >= 0; i--) - { - if (propVals[i] === LEAVE) - { + for (var i = propVals.length - 1; i >= 0; i--) { + if (propVals[i] === LEAVE) { //emitCloseTag(i); tags2close.push(i); propVals[i] = false; - } - else if (propVals[i] === STAY) - { + } else if (propVals[i] === STAY) { //emitCloseTag(i); tags2close.push(i); } } - for (var i = 0; i < propVals.length; i++) - { - if (propVals[i] === ENTER || propVals[i] === STAY) - { + for (var i = 0; i < propVals.length; i++) { + if (propVals[i] === ENTER || propVals[i] === STAY) { propVals[i] = true; } } @@ -201,9 +173,9 @@ function getTXTFromAtext(pad, atext, authorColors) } // end if (propChanged) var chars = o.chars; - if (o.lines) - { - chars--; // exclude newline at end of line, if present + if (o.lines) { + // exclude newline at end of line, if present + chars--; } var s = taker.take(chars); @@ -220,19 +192,19 @@ function getTXTFromAtext(pad, atext, authorColors) } // end iteration over spans in line var tags2close = []; - for (var i = propVals.length - 1; i >= 0; i--) - { - if (propVals[i]) - { + for (var i = propVals.length - 1; i >= 0; i--) { + if (propVals[i]) { tags2close.push(i); propVals[i] = false; } } } // end processNextChars + processNextChars(text.length - idx); return(assem.toString()); } // end getLineHTML + var pieces = [css]; // Need to deal with constraints imposed on HTML lists; can @@ -242,41 +214,44 @@ function getTXTFromAtext(pad, atext, authorColors) // so we want to do something reasonable there. We also // want to deal gracefully with blank lines. // => keeps track of the parents level of indentation - for (var i = 0; i < textLines.length; i++) - { + for (var i = 0; i < textLines.length; i++) { var line = _analyzeLine(textLines[i], attribLines[i], apool); var lineContent = getLineTXT(line.text, line.aline); - if(line.listTypeName == "bullet"){ + + if (line.listTypeName == "bullet") { lineContent = "* " + lineContent; // add a bullet } - if(line.listLevel > 0){ - for (var j = line.listLevel - 1; j >= 0; j--){ + + if (line.listLevel > 0) { + for (var j = line.listLevel - 1; j >= 0; j--) { pieces.push('\t'); } - if(line.listTypeName == "number"){ + + if (line.listTypeName == "number") { pieces.push(line.listLevel + ". "); // This is bad because it doesn't truly reflect what the user // sees because browsers do magic on nested
  1. s } + pieces.push(lineContent, '\n'); - }else{ + } else { pieces.push(lineContent, '\n'); } } return pieces.join(''); } + exports.getTXTFromAtext = getTXTFromAtext; -exports.getPadTXTDocument = function (padId, revNum, callback) +exports.getPadTXTDocument = function(padId, revNum, callback) { - padManager.getPad(padId, function (err, pad) - { - if(ERR(err, callback)) return; + padManager.getPad(padId, function(err, pad) { + if (ERR(err, callback)) return; + + getPadTXT(pad, revNum, function(err, html) { + if (ERR(err, callback)) return; - getPadTXT(pad, revNum, function (err, html) - { - if(ERR(err, callback)) return; callback(null, html); }); }); diff --git a/src/node/utils/ImportEtherpad.js b/src/node/utils/ImportEtherpad.js index bf1129cb9..1ff8b9b04 100644 --- a/src/node/utils/ImportEtherpad.js +++ b/src/node/utils/ImportEtherpad.js @@ -18,57 +18,56 @@ var log4js = require('log4js'); var async = require("async"); var db = require("../db/DB").db; -exports.setPadRaw = function(padId, records, callback){ +exports.setPadRaw = function(padId, records, callback) +{ records = JSON.parse(records); - async.eachSeries(Object.keys(records), function(key, cb){ - var value = records[key] + async.eachSeries(Object.keys(records), function(key, cb) { + var value = records[key]; - if(!value){ + if (!value) { return setImmediate(cb); } - // Author data - if(value.padIDs){ - // rewrite author pad ids + if (value.padIDs) { + // Author data - rewrite author pad ids value.padIDs[padId] = 1; var newKey = key; // Does this author already exist? - db.get(key, function(err, author){ - if(author){ - // Yes, add the padID to the author.. - if( Object.prototype.toString.call(author) === '[object Array]'){ + db.get(key, function(err, author) { + if (author) { + // Yes, add the padID to the author + if (Object.prototype.toString.call(author) === '[object Array]') { author.padIDs.push(padId); } value = author; - }else{ + } else { // No, create a new array with the author info in value.padIDs = [padId]; } }); - - // Not author data, probably pad data - }else{ - // we can split it to look to see if its pad data + } else { + // Not author data, probably pad data + // we can split it to look to see if it's pad data var oldPadId = key.split(":"); - // we know its pad data.. - if(oldPadId[0] === "pad"){ - + // we know it's pad data + if (oldPadId[0] === "pad") { // so set the new pad id for the author oldPadId[1] = padId; - + // and create the value var newKey = oldPadId.join(":"); // create the new key } - } + // Write the value to the server db.set(newKey, value); setImmediate(cb); - }, function(){ + }, + function() { callback(null, true); }); } diff --git a/src/node/utils/ImportHtml.js b/src/node/utils/ImportHtml.js index d71e27201..04037eab5 100644 --- a/src/node/utils/ImportHtml.js +++ b/src/node/utils/ImportHtml.js @@ -36,19 +36,22 @@ function setPadHTML(pad, html, callback) // Convert a dom tree into a list of lines and attribute liens // using the content collector object var cc = contentcollector.makeContentCollector(true, null, pad.pool); - try{ // we use a try here because if the HTML is bad it will blow up + try { + // we use a try here because if the HTML is bad it will blow up cc.collectContent(doc); - }catch(e){ + } catch(e) { apiLogger.warn("HTML was not properly formed", e); - return callback(e); // We don't process the HTML because it was bad.. + + // don't process the HTML because it was bad + return callback(e); } var result = cc.finish(); apiLogger.debug('Lines:'); + var i; - for (i = 0; i < result.lines.length; i += 1) - { + for (i = 0; i < result.lines.length; i += 1) { apiLogger.debug('Line ' + (i + 1) + ' text: ' + result.lines[i]); apiLogger.debug('Line ' + (i + 1) + ' attributes: ' + result.lineAttribs[i]); } @@ -59,18 +62,15 @@ function setPadHTML(pad, html, callback) apiLogger.debug(newText); var newAttribs = result.lineAttribs.join('|1+1') + '|1+1'; - function eachAttribRun(attribs, func /*(startInNewText, endInNewText, attribs)*/ ) - { + function eachAttribRun(attribs, func /*(startInNewText, endInNewText, attribs)*/ ) { var attribsIter = Changeset.opIterator(attribs); var textIndex = 0; var newTextStart = 0; var newTextEnd = newText.length; - while (attribsIter.hasNext()) - { + while (attribsIter.hasNext()) { var op = attribsIter.next(); var nextIndex = textIndex + op.chars; - if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) - { + if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) { func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs); } textIndex = nextIndex; @@ -81,13 +81,13 @@ function setPadHTML(pad, html, callback) var builder = Changeset.builder(1); // assemble each line into the builder - eachAttribRun(newAttribs, function(start, end, attribs) - { + eachAttribRun(newAttribs, function(start, end, attribs) { builder.insert(newText.substring(start, end), attribs); }); // the changeset is ready! var theChangeset = builder.toString(); + apiLogger.debug('The changeset: ' + theChangeset); pad.setText("\n"); pad.appendRevision(theChangeset); diff --git a/src/node/utils/padDiff.js b/src/node/utils/padDiff.js index 24d5bb0c2..33938b19c 100644 --- a/src/node/utils/padDiff.js +++ b/src/node/utils/padDiff.js @@ -1,336 +1,348 @@ var Changeset = require("../../static/js/Changeset"); var async = require("async"); var exportHtml = require('./ExportHtml'); - -function PadDiff (pad, fromRev, toRev){ - //check parameters - if(!pad || !pad.id || !pad.atext || !pad.pool) - { + +function PadDiff (pad, fromRev, toRev) { + // check parameters + if (!pad || !pad.id || !pad.atext || !pad.pool) { throw new Error('Invalid pad'); } - + var range = pad.getValidRevisionRange(fromRev, toRev); - if(!range) { throw new Error('Invalid revision range.' + + if (!range) { + throw new Error('Invalid revision range.' + ' startRev: ' + fromRev + - ' endRev: ' + toRev); } - + ' endRev: ' + toRev); + } + this._pad = pad; this._fromRev = range.startRev; this._toRev = range.endRev; this._html = null; this._authors = []; } - -PadDiff.prototype._isClearAuthorship = function(changeset){ - //unpack + +PadDiff.prototype._isClearAuthorship = function(changeset) { + // unpack var unpacked = Changeset.unpack(changeset); - - //check if there is nothing in the charBank - if(unpacked.charBank !== "") + + // check if there is nothing in the charBank + if (unpacked.charBank !== "") { return false; - - //check if oldLength == newLength - if(unpacked.oldLen !== unpacked.newLen) + } + + // check if oldLength == newLength + if (unpacked.oldLen !== unpacked.newLen) { return false; - - //lets iterator over the operators + } + + // lets iterator over the operators var iterator = Changeset.opIterator(unpacked.ops); - - //get the first operator, this should be a clear operator + + // get the first operator, this should be a clear operator var clearOperator = iterator.next(); - - //check if there is only one operator - if(iterator.hasNext() === true) + + // check if there is only one operator + if (iterator.hasNext() === true) { return false; - - //check if this operator doesn't change text - if(clearOperator.opcode !== "=") + } + + // check if this operator doesn't change text + if (clearOperator.opcode !== "=") { return false; - - //check that this operator applys to the complete text - //if the text ends with a new line, its exactly one character less, else it has the same length - if(clearOperator.chars !== unpacked.oldLen-1 && clearOperator.chars !== unpacked.oldLen) + } + + // check that this operator applys to the complete text + // if the text ends with a new line, its exactly one character less, else it has the same length + if (clearOperator.chars !== unpacked.oldLen-1 && clearOperator.chars !== unpacked.oldLen) { return false; - + } + var attributes = []; - Changeset.eachAttribNumber(changeset, function(attrNum){ + Changeset.eachAttribNumber(changeset, function(attrNum) { attributes.push(attrNum); }); - - //check that this changeset uses only one attribute - if(attributes.length !== 1) + + // check that this changeset uses only one attribute + if (attributes.length !== 1) { return false; - + } + var appliedAttribute = this._pad.pool.getAttrib(attributes[0]); - - //check if the applied attribute is an anonymous author attribute - if(appliedAttribute[0] !== "author" || appliedAttribute[1] !== "") + + // check if the applied attribute is an anonymous author attribute + if (appliedAttribute[0] !== "author" || appliedAttribute[1] !== "") { return false; - + } + return true; }; - -PadDiff.prototype._createClearAuthorship = function(rev, callback){ + +PadDiff.prototype._createClearAuthorship = function(rev, callback) { var self = this; - this._pad.getInternalRevisionAText(rev, function(err, atext){ - if(err){ - return callback(err); - } - - //build clearAuthorship changeset + this._pad.getInternalRevisionAText(rev, function(err, atext) { + if (err) { + return callback(err); + } + + // build clearAuthorship changeset var builder = Changeset.builder(atext.text.length); builder.keepText(atext.text, [['author','']], self._pad.pool); var changeset = builder.toString(); - + callback(null, changeset); }); }; - -PadDiff.prototype._createClearStartAtext = function(rev, callback){ + +PadDiff.prototype._createClearStartAtext = function(rev, callback) { var self = this; - - //get the atext of this revision - this._pad.getInternalRevisionAText(rev, function(err, atext){ - if(err){ - return callback(err); - } - - //create the clearAuthorship changeset - self._createClearAuthorship(rev, function(err, changeset){ - if(err){ - return callback(err); - } - - try { - //apply the clearAuthorship changeset - var newAText = Changeset.applyToAText(changeset, atext, self._pad.pool); - } catch(err) { - return callback(err) - } - - callback(null, newAText); - }); - }); -}; - -PadDiff.prototype._getChangesetsInBulk = function(startRev, count, callback) { - var self = this; - - //find out which revisions we need - var revisions = []; - for(var i=startRev;i<(startRev+count) && i<=this._pad.head;i++){ - revisions.push(i); - } - - var changesets = [], authors = []; - - //get all needed revisions - async.forEach(revisions, function(rev, callback){ - self._pad.getRevision(rev, function(err, revision){ - if(err){ + + // get the atext of this revision + this._pad.getInternalRevisionAText(rev, function(err, atext) { + if (err) { + return callback(err); + } + + // create the clearAuthorship changeset + self._createClearAuthorship(rev, function(err, changeset) { + if (err) { return callback(err); } - + + try { + // apply the clearAuthorship changeset + var newAText = Changeset.applyToAText(changeset, atext, self._pad.pool); + } catch(err) { + return callback(err) + } + + callback(null, newAText); + }); + }); +}; + +PadDiff.prototype._getChangesetsInBulk = function(startRev, count, callback) { + var self = this; + + // find out which revisions we need + var revisions = []; + for (var i = startRev; i < (startRev + count) && i <= this._pad.head; i++) { + revisions.push(i); + } + + var changesets = [], authors = []; + + // get all needed revisions + async.forEach(revisions, function(rev, callback) { + self._pad.getRevision(rev, function(err, revision) { + if (err) { + return callback(err); + } + var arrayNum = rev-startRev; - + changesets[arrayNum] = revision.changeset; authors[arrayNum] = revision.meta.author; - + callback(); }); - }, function(err){ + }, + function(err) { callback(err, changesets, authors); }); }; - + PadDiff.prototype._addAuthors = function(authors) { var self = this; - //add to array if not in the array - authors.forEach(function(author){ - if(self._authors.indexOf(author) == -1){ + + // add to array if not in the array + authors.forEach(function(author) { + if (self._authors.indexOf(author) == -1) { self._authors.push(author); } }); }; - + PadDiff.prototype._createDiffAtext = function(callback) { var self = this; var bulkSize = 100; - - //get the cleaned startAText - self._createClearStartAtext(self._fromRev, function(err, atext){ - if(err) { return callback(err); } - + + // get the cleaned startAText + self._createClearStartAtext(self._fromRev, function(err, atext) { + if (err) { return callback(err); } + var superChangeset = null; - + var rev = self._fromRev + 1; - - //async while loop + + // async while loop async.whilst( - //loop condition + // loop condition function () { return rev <= self._toRev; }, - - //loop body + + // loop body function (callback) { - //get the bulk - self._getChangesetsInBulk(rev,bulkSize,function(err, changesets, authors){ + // get the bulk + self._getChangesetsInBulk(rev,bulkSize,function(err, changesets, authors) { var addedAuthors = []; - - //run trough all changesets - for(var i=0;i 0) { if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) { curLine++; @@ -384,22 +396,25 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) { curLineNextOp.chars = 0; curLineOpIter = Changeset.opIterator(alines_get(curLine)); } + if (!curLineNextOp.chars) { curLineOpIter.next(curLineNextOp); } + var charsToUse = Math.min(numChars, curLineNextOp.chars); + func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && curLineNextOp.lines > 0); numChars -= charsToUse; curLineNextOp.chars -= charsToUse; curChar += charsToUse; } - + if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) { curLine++; curChar = 0; } } - + function skip(N, L) { if (L) { curLine += L; @@ -412,27 +427,29 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) { } } } - + function nextText(numChars) { var len = 0; var assem = Changeset.stringAssembler(); var firstString = lines_get(curLine).substring(curChar); len += firstString.length; assem.append(firstString); - + var lineNum = curLine + 1; + while (len < numChars) { var nextString = lines_get(lineNum); len += nextString.length; assem.append(nextString); lineNum++; } - + return assem.toString().substring(0, numChars); } - + function cachedStrFunc(func) { var cache = {}; + return function (s) { if (!cache[s]) { cache[s] = func(s); @@ -440,57 +457,59 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) { return cache[s]; }; } - + var attribKeys = []; var attribValues = []; - - //iterate over all operators of this changeset + + // iterate over all operators of this changeset while (csIter.hasNext()) { var csOp = csIter.next(); - - if (csOp.opcode == '=') { + + if (csOp.opcode == '=') { var textBank = nextText(csOp.chars); - + // decide if this equal operator is an attribution change or not. We can see this by checkinf if attribs is set. // If the text this operator applies to is only a star, than this is a false positive and should be ignored if (csOp.attribs && textBank != "*") { var deletedAttrib = apool.putAttrib(["removed", true]); var authorAttrib = apool.putAttrib(["author", ""]); - + attribKeys.length = 0; attribValues.length = 0; Changeset.eachAttribNumber(csOp.attribs, function (n) { attribKeys.push(apool.getAttribKey(n)); attribValues.push(apool.getAttribValue(n)); - - if(apool.getAttribKey(n) === "author"){ + + if (apool.getAttribKey(n) === "author") { authorAttrib = n; } }); - + var undoBackToAttribs = cachedStrFunc(function (attribs) { var backAttribs = []; for (var i = 0; i < attribKeys.length; i++) { var appliedKey = attribKeys[i]; var appliedValue = attribValues[i]; var oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, apool); + if (appliedValue != oldValue) { backAttribs.push([appliedKey, oldValue]); } } + return Changeset.makeAttribsString('=', backAttribs, apool); }); - + var oldAttribsAddition = "*" + Changeset.numToString(deletedAttrib) + "*" + Changeset.numToString(authorAttrib); - + var textLeftToProcess = textBank; - - while(textLeftToProcess.length > 0){ - //process till the next line break or process only one line break + + while(textLeftToProcess.length > 0) { + // process till the next line break or process only one line break var lengthToProcess = textLeftToProcess.indexOf("\n"); var lineBreak = false; - switch(lengthToProcess){ - case -1: + switch(lengthToProcess) { + case -1: lengthToProcess=textLeftToProcess.length; break; case 0: @@ -498,27 +517,28 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) { lengthToProcess=1; break; } - - //get the text we want to procceed in this step + + // get the text we want to procceed in this step var processText = textLeftToProcess.substr(0, lengthToProcess); + textLeftToProcess = textLeftToProcess.substr(lengthToProcess); - - if(lineBreak){ - builder.keep(1, 1); //just skip linebreaks, don't do a insert + keep for a linebreak - - //consume the attributes of this linebreak - consumeAttribRuns(1, function(){}); + + if (lineBreak) { + builder.keep(1, 1); // just skip linebreaks, don't do a insert + keep for a linebreak + + // consume the attributes of this linebreak + consumeAttribRuns(1, function() {}); } else { - //add the old text via an insert, but add a deletion attribute + the author attribute of the author who deleted it + // add the old text via an insert, but add a deletion attribute + the author attribute of the author who deleted it var textBankIndex = 0; consumeAttribRuns(lengthToProcess, function (len, attribs, endsLine) { - //get the old attributes back + // get the old attributes back var attribs = (undoBackToAttribs(attribs) || "") + oldAttribsAddition; - + builder.insert(processText.substr(textBankIndex, len), attribs); textBankIndex += len; }); - + builder.keep(lengthToProcess, 0); } } @@ -531,16 +551,16 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) { } else if (csOp.opcode == '-') { var textBank = nextText(csOp.chars); var textBankIndex = 0; - + consumeAttribRuns(csOp.chars, function (len, attribs, endsLine) { builder.insert(textBank.substr(textBankIndex, len), attribs + csOp.attribs); textBankIndex += len; }); } } - + return Changeset.checkRep(builder.toString()); }; - -//export the constructor + +// export the constructor module.exports = PadDiff; diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index cd2ed3305..fbd0e1d97 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -4,12 +4,14 @@ var npm = require("npm"); var request = require("request"); var npmIsLoaded = false; -var withNpm = function (npmfn) { - if(npmIsLoaded) return npmfn(); - npm.load({}, function (er) { +var withNpm = function(npmfn) { + if (npmIsLoaded) return npmfn(); + + npm.load({}, function(er) { if (er) return npmfn(er); + npmIsLoaded = true; - npm.on("log", function (message) { + npm.on("log", function(message) { console.log('npm: ',message) }); npmfn(); @@ -17,26 +19,33 @@ var withNpm = function (npmfn) { } var tasks = 0 + function wrapTaskCb(cb) { - tasks++ + tasks++; + return function() { cb && cb.apply(this, arguments); tasks--; - if(tasks == 0) onAllTasksFinished(); + if (tasks == 0) onAllTasksFinished(); } } + function onAllTasksFinished() { - hooks.aCallAll("restartServer", {}, function () {}); + hooks.aCallAll("restartServer", {}, function() {}); } exports.uninstall = function(plugin_name, cb) { cb = wrapTaskCb(cb); - withNpm(function (er) { + + withNpm(function(er) { if (er) return cb && cb(er); - npm.commands.uninstall([plugin_name], function (er) { + + npm.commands.uninstall([plugin_name], function(er) { if (er) return cb && cb(er); - hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er, data) { + + hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function(er, data) { if (er) return cb(er); + plugins.update(cb); }); }); @@ -44,13 +53,17 @@ exports.uninstall = function(plugin_name, cb) { }; exports.install = function(plugin_name, cb) { - cb = wrapTaskCb(cb) - withNpm(function (er) { + cb = wrapTaskCb(cb); + + withNpm(function(er) { if (er) return cb && cb(er); - npm.commands.install([plugin_name], function (er) { + + npm.commands.install([plugin_name], function(er) { if (er) return cb && cb(er); - hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er, data) { + + hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function(er, data) { if (er) return cb(er); + plugins.update(cb); }); }); @@ -63,41 +76,53 @@ var cacheTimestamp = 0; exports.getAvailablePlugins = function(maxCacheAge, cb) { request("https://static.etherpad.org/plugins.json", function(er, response, plugins){ if (er) return cb && cb(er); - if(exports.availablePlugins && maxCacheAge && Math.round(+new Date/1000)-cacheTimestamp <= maxCacheAge) { - return cb && cb(null, exports.availablePlugins) + + if (exports.availablePlugins && maxCacheAge && Math.round(+ new Date / 1000) - cacheTimestamp <= maxCacheAge) { + return cb && cb(null, exports.availablePlugins); } + try { plugins = JSON.parse(plugins); } catch (err) { console.error('error parsing plugins.json:', err); plugins = []; } + exports.availablePlugins = plugins; - cacheTimestamp = Math.round(+new Date/1000); - cb && cb(null, plugins) + cacheTimestamp = Math.round(+ new Date / 1000); + + cb && cb(null, plugins); }); }; exports.search = function(searchTerm, maxCacheAge, cb) { exports.getAvailablePlugins(maxCacheAge, function(er, results) { - if(er) return cb && cb(er); + if (er) return cb && cb(er); + var res = {}; - if (searchTerm) + + if (searchTerm) { searchTerm = searchTerm.toLowerCase(); - for (var pluginName in results) { // for every available plugin + } + + for (var pluginName in results) { + // for every available plugin if (pluginName.indexOf(plugins.prefix) != 0) continue; // TODO: Also search in keywords here! - if(searchTerm && !~results[pluginName].name.toLowerCase().indexOf(searchTerm) + if (searchTerm && !~results[pluginName].name.toLowerCase().indexOf(searchTerm) && (typeof results[pluginName].description != "undefined" && !~results[pluginName].description.toLowerCase().indexOf(searchTerm) ) - ){ - if(typeof results[pluginName].description === "undefined"){ + ) { + if (typeof results[pluginName].description === "undefined") { console.debug('plugin without Description: %s', results[pluginName].name); } + continue; } + res[pluginName] = results[pluginName]; } - cb && cb(null, res) - }) + + cb && cb(null, res); + }); }; diff --git a/src/static/js/pluginfw/plugins.js b/src/static/js/pluginfw/plugins.js index 7cb4b1bdc..38d6b68c0 100644 --- a/src/static/js/pluginfw/plugins.js +++ b/src/static/js/pluginfw/plugins.js @@ -55,6 +55,7 @@ exports.formatHooks = function (hook_set_name) { exports.callInit = function (cb) { var hooks = require("./hooks"); + async.map( Object.keys(exports.plugins), function (plugin_name, cb) { @@ -83,6 +84,7 @@ exports.update = function (cb) { exports.getPackages(function (er, packages) { var parts = []; var plugins = {}; + // Load plugin metadata ep.json async.forEach( Object.keys(packages), @@ -106,6 +108,7 @@ exports.getPackages = function (cb) { var dir = path.resolve(npm.dir, '..'); readInstalled(dir, function (er, data) { if (er) cb(er, null); + var packages = {}; function flatten(deps) { _.chain(deps).keys().each(function (name) { @@ -116,12 +119,12 @@ exports.getPackages = function (cb) { delete packages[name].dependencies; delete packages[name].parent; } - + // I don't think we need recursion //if (deps[name].dependencies !== undefined) flatten(deps[name].dependencies); }); } - + var tmp = {}; tmp[data.name] = data; flatten(tmp[data.name].dependencies);