diff --git a/README.md b/README.md index ab241333f..aaf2c8fbc 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,9 @@ You can join the [mailinglist](http://groups.google.com/group/etherpad-lite-dev) You also help the project, if you only host a Etherpad Lite instance and share your experience with us. +Please consider using [jshint](http://www.jshint.com/about/) if you plan to +contribute to Etherpad Lite. + # Modules created for this project * [ueberDB](https://github.com/Pita/ueberDB) "transforms every database into a object key value store" - manages all database access diff --git a/bin/jshint.sh b/bin/jshint.sh new file mode 100755 index 000000000..4dea73961 --- /dev/null +++ b/bin/jshint.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ -d "../bin" ]; then + cd "../" +fi + +JSHINT=./node_modules/jshint/bin/hint + +$JSHINT ./node/ diff --git a/node/db/API.js b/node/db/API.js index 09cc95afc..a99012c17 100644 --- a/node/db/API.js +++ b/node/db/API.js @@ -29,7 +29,7 @@ var sessionManager = require("./SessionManager"); var async = require("async"); var exportHtml = require("../utils/ExportHtml"); var importHtml = require("../utils/ImportHtml"); -var cleanText = require("./Pad").cleanText; +var cleanText = require("../utils/cleantext").cleanText; /**********************/ /**GROUP FUNCTIONS*****/ @@ -63,7 +63,7 @@ exports.listSessionsOfAuthor = sessionManager.listSessionsOfAuthor; /************************/ /** -getText(padID, [rev]) returns the text of a pad +getText(padID, [rev]) returns the text of a pad Example returns: @@ -78,14 +78,14 @@ exports.getText = function(padID, rev, callback) callback = rev; rev = undefined; } - + //check if rev is a number if(rev !== undefined && typeof rev != "number") { //try to parse the number - if(!isNaN(parseInt(rev))) + if(!isNaN(parseInt(rev, 10))) { - rev = parseInt(rev); + rev = parseInt(rev, 10); } else { @@ -93,26 +93,26 @@ exports.getText = function(padID, rev, callback) return; } } - + //ensure this is not a negativ 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")); 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) { @@ -122,16 +122,16 @@ exports.getText = function(padID, rev, callback) 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; - + data = {text: atext.text}; - + callback(null, data); - }) + }); } //the client wants the latest text, lets return it to him else @@ -139,10 +139,10 @@ exports.getText = function(padID, rev, callback) callback(null, {"text": pad.text()}); } }); -} +}; /** -setText(padID, text) sets the text of a pad +setText(padID, text) sets the text of a pad Example returns: @@ -151,22 +151,22 @@ Example returns: {code: 1, message:"text too long", data: null} */ exports.setText = function(padID, text, callback) -{ +{ //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 padMessageHandler.updatePadClients(pad, callback); }); -} +}; /** -getHTML(padID, [rev]) returns the html of a pad +getHTML(padID, [rev]) returns the html of a pad Example returns: @@ -178,14 +178,14 @@ exports.getHTML = function(padID, rev, callback) if(typeof rev == "function") { callback = rev; - rev = undefined; + rev = undefined; } if (rev !== undefined && typeof rev != "number") { - if (!isNaN(parseInt(rev))) + if (!isNaN(parseInt(rev, 10))) { - rev = parseInt(rev); + rev = parseInt(rev, 10); } else { @@ -209,7 +209,7 @@ exports.getHTML = function(padID, rev, callback) getPadSafe(padID, true, function(err, pad) { if(ERR(err, callback)) return; - + //the client asked for a special revision if(rev !== undefined) { @@ -219,8 +219,8 @@ exports.getHTML = function(padID, rev, callback) callback(new customError("rev is higher than the head revision of the pad","apierror")); return; } - - //get the html of this revision + + //get the html of this revision exportHtml.getPadHTML(pad, rev, function(err, html) { if(ERR(err, callback)) return; @@ -234,14 +234,14 @@ exports.getHTML = function(padID, rev, callback) exportHtml.getPadHTML(pad, undefined, function (err, html) { if(ERR(err, callback)) return; - + data = {html: html}; - + callback(null, data); }); } }); -} +}; exports.setHTML = function(padID, html, callback) { @@ -257,14 +257,14 @@ exports.setHTML = function(padID, html, callback) padMessageHandler.updatePadClients(pad, callback); }); -} +}; /*****************/ /**PAD FUNCTIONS */ /*****************/ /** -getRevisionsCount(padID) returns the number of revisions of this pad +getRevisionsCount(padID) returns the number of revisions of this pad Example returns: @@ -277,13 +277,13 @@ exports.getRevisionsCount = function(padID, callback) getPadSafe(padID, true, function(err, pad) { if(ERR(err, callback)) return; - + callback(null, {revisions: pad.getHeadRevisionNumber()}); }); -} +}; /** -createPad(padName [, text]) creates a new pad in this group +createPad(padName [, text]) creates a new pad in this group Example returns: @@ -291,24 +291,24 @@ 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 && padID.indexOf("$") != -1) { callback(new customError("createPad can't create group pads","apierror")); 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: @@ -320,13 +320,13 @@ exports.deletePad = function(padID, callback) getPadSafe(padID, true, function(err, pad) { 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: @@ -339,7 +339,7 @@ exports.getReadOnlyID = function(padID, callback) getPadSafe(padID, true, function(err) { if(ERR(err, callback)) return; - + //get the readonlyId readOnlyManager.getReadOnlyId(padID, function(err, readOnlyId) { @@ -347,10 +347,10 @@ exports.getReadOnlyID = function(padID, callback) callback(null, {readOnlyID: readOnlyId}); }); }); -} +}; /** -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: @@ -370,20 +370,20 @@ exports.setPublicStatus = function(padID, publicStatus, callback) 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 pad.setPublicStatus(publicStatus); - + callback(); }); -} +}; /** -getPublicStatus(padID) return true of false +getPublicStatus(padID) return true of false Example returns: @@ -398,18 +398,18 @@ exports.getPublicStatus = function(padID, callback) 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; - + 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: @@ -424,21 +424,21 @@ exports.setPassword = function(padID, password, callback) 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 pad.setPassword(password); - + callback(); }); -} +}; /** -isPasswordProtected(padID) returns true or false +isPasswordProtected(padID) returns true or false Example returns: @@ -458,10 +458,10 @@ exports.isPasswordProtected = function(padID, callback) getPadSafe(padID, true, function(err, pad) { if(ERR(err, callback)) return; - + callback(null, {isPasswordProtected: pad.isPasswordProtected()}); }); -} +}; /******************************/ /** INTERNAL HELPER FUNCTIONS */ @@ -469,8 +469,8 @@ exports.isPasswordProtected = function(padID, callback) //checks if a number is an int function is_int(value) -{ - return (parseFloat(value) == parseInt(value)) && !isNaN(value) +{ + return (parseFloat(value) == parseInt(value, 10)) && !isNaN(value); } //gets a pad safe @@ -488,26 +488,26 @@ function getPadSafe(padID, shouldExist, text, callback) 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")); 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) + if(!exists && shouldExist) { callback(new customError("padID does not exist","apierror")); } //does exists, but shouldn't - else if(exists == true && shouldExist == false) + else if(exists && !shouldExist) { callback(new customError("padID does already exist","apierror")); } diff --git a/node/db/AuthorManager.js b/node/db/AuthorManager.js index f4f42d112..3fcd3fbe4 100644 --- a/node/db/AuthorManager.js +++ b/node/db/AuthorManager.js @@ -31,14 +31,14 @@ exports.doesAuthorExists = function (authorID, callback) db.get("globalAuthor:" + authorID, function (err, author) { if(ERR(err, callback)) return; - callback(null, author != null); + callback(null, author ? true : false); }); -} +}; /** - * Returns the AuthorID for a token. - * @param {String} token The token - * @param {Function} callback callback (err, author) + * Returns the AuthorID for a token. + * @param {String} token The token + * @param {Function} callback callback (err, author) */ exports.getAuthor4Token = function (token, callback) { @@ -48,52 +48,54 @@ exports.getAuthor4Token = function (token, callback) //return only the sub value authorID callback(null, author ? author.authorID : author); }); -} +}; /** - * Returns the AuthorID for a mapper. + * Returns the AuthorID for a mapper. * @param {String} token The mapper - * @param {Function} callback callback (err, author) + * @param {Function} callback callback (err, author) */ exports.createAuthorIfNotExistsFor = function (authorMapper, name, callback) { mapAuthorWithDBKey("mapper2author", authorMapper, function(err, author) { if(ERR(err, callback)) return; - + //set the name of this author if(name) + { exports.setAuthorName(author.authorID, name); - + } + //return the authorID callback(null, author); }); -} +}; /** * Returns the AuthorID for a mapper. We can map using a mapperkey, * so far this is token2author and mapper2author - * @param {String} mapperkey The database key name for this mapper + * @param {String} mapperkey The database key name for this mapper * @param {String} mapper The mapper - * @param {Function} callback callback (err, author) + * @param {Function} callback callback (err, author) */ function mapAuthorWithDBKey (mapperkey, mapper, callback) -{ +{ //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) + if(!author) { exports.createAuthor(null, function(err, author) { if(ERR(err, callback)) return; - + //create the token2author relation db.set(mapperkey + ":" + mapper, author.authorID); - + //return the author callback(null, author); }); @@ -103,7 +105,7 @@ function mapAuthorWithDBKey (mapperkey, mapper, callback) { //update the timestamp of this author db.setSub("globalAuthor:" + author, ["timestamp"], new Date().getTime()); - + //return the author callback(null, {authorID: author}); } @@ -111,22 +113,22 @@ function mapAuthorWithDBKey (mapperkey, mapper, callback) } /** - * Internal function that creates the database entry for an author - * @param {String} name The name of the author + * Internal function that creates the database entry for an author + * @param {String} name The name of the author */ exports.createAuthor = function(name, callback) { //create the new author name var author = "a." + randomString(16); - + //create the globalAuthors db entry var authorObj = {"colorId" : Math.floor(Math.random()*32), "name": name, "timestamp": new Date().getTime()}; - + //set the global author db entry db.set("globalAuthor:" + author, authorObj); - + callback(null, {authorID: author}); -} +}; /** * Returns the Author Obj of the author @@ -136,7 +138,7 @@ exports.createAuthor = function(name, callback) exports.getAuthor = function (author, callback) { db.get("globalAuthor:" + author, callback); -} +}; /** * Returns the color Id of the author @@ -146,7 +148,7 @@ exports.getAuthor = function (author, callback) exports.getAuthorColorId = function (author, callback) { db.getSub("globalAuthor:" + author, ["colorId"], callback); -} +}; /** * Sets the color Id of the author @@ -156,7 +158,7 @@ exports.getAuthorColorId = function (author, callback) exports.setAuthorColorId = function (author, colorId, callback) { db.setSub("globalAuthor:" + author, ["colorId"], colorId, callback); -} +}; /** * Returns the name of the author @@ -166,7 +168,7 @@ exports.setAuthorColorId = function (author, colorId, callback) exports.getAuthorName = function (author, callback) { db.getSub("globalAuthor:" + author, ["name"], callback); -} +}; /** * Sets the name of the author @@ -176,12 +178,12 @@ exports.getAuthorName = function (author, callback) exports.setAuthorName = function (author, name, callback) { db.setSub("globalAuthor:" + author, ["name"], name, callback); -} +}; /** * Generates a random String with the given length. Is needed to generate the Author Ids */ -function randomString(len) +function randomString(len) { var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; var randomstring = ''; diff --git a/node/db/DB.js b/node/db/DB.js index 7273c83e3..2f8f1ae0f 100644 --- a/node/db/DB.js +++ b/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 */ @@ -33,14 +33,14 @@ 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 + //there was an error while initializing the database, output it and stop if(err) { console.error("ERROR: Problem while initalizing the database"); @@ -50,8 +50,8 @@ exports.init = function(callback) //everything ok else { - exports.db = db; + exports.db = db; callback(null); } }); -} +}; diff --git a/node/db/GroupManager.js b/node/db/GroupManager.js index 473ea9b77..d5421cebb 100644 --- a/node/db/GroupManager.js +++ b/node/db/GroupManager.js @@ -17,29 +17,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + var ERR = require("async-stacktrace"); var customError = require("../utils/customError"); var db = require("./DB").db; var async = require("async"); var padManager = require("./PadManager"); var sessionManager = require("./SessionManager"); - + exports.deleteGroup = function(groupID, callback) { var group; async.series([ - //ensure group exists + //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) + if(!_group) { callback(new customError("groupID does not exist","apierror")); } @@ -60,14 +60,14 @@ exports.deleteGroup = function(groupID, callback) { padIDs.push(i); } - - //loop trough all pads and delete them + + //loop trough 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); @@ -79,18 +79,21 @@ exports.deleteGroup = function(groupID, callback) db.get("group2sessions:" + groupID, function (err, group2sessions) { if(ERR(err, callback)) return; - + //skip if there is no group2sessions entry - if(group2sessions == null) {callback(); return} - + if(!group2sessions) { + callback(); + return; + } + //collect all sessions in an array, that allows us to use async.forEach var sessions = []; for(var i in group2sessions.sessionsIDs) { sessions.push(i); } - - //loop trough all sessions and delete them + + //loop trough all sessions and delete them async.forEach(sessions, function(session, callback) { sessionManager.deleteSession(session, callback); @@ -109,27 +112,27 @@ exports.deleteGroup = function(groupID, callback) 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; - callback(null, group != null); + callback(null, group ? true : false); }); -} +}; exports.createGroup = function(callback) { //search for non existing groupID var groupID = "g." + randomString(16); - + //create the group db.set("group:" + groupID, {pads: {}}); callback(null, {groupID: groupID}); -} +}; exports.createGroupIfNotExistsFor = function(groupMapper, callback) { @@ -139,22 +142,22 @@ exports.createGroupIfNotExistsFor = function(groupMapper, callback) callback(new customError("groupMapper is no string","apierror")); return; } - + //try to get a group for this mapper db.get("mapper2group:"+groupMapper, function(err, groupID) { if(ERR(err, callback)) return; - + //there is no group for this mapper, let's create a group - if(groupID == null) + if(!groupID) { exports.createGroup(function(err, responseObj) { if(ERR(err, callback)) return; - + //create the mapper entry for this group db.set("mapper2group:"+groupMapper, responseObj.groupID); - + callback(null, responseObj); }); } @@ -165,7 +168,7 @@ exports.createGroupIfNotExistsFor = function(groupMapper, callback) callback(null, {groupID: groupID}); } }); -} +}; exports.createGroupPad = function(groupID, padName, text, callback) { @@ -173,15 +176,15 @@ exports.createGroupPad = function(groupID, padName, text, callback) var padID = groupID + "$" + padName; async.series([ - //ensure group exists + //ensure group exists function (callback) { exports.doesGroupExist(groupID, function(err, exists) { if(ERR(err, callback)) return; - + //group does not exist - if(exists == false) + if(!exists) { callback(new customError("groupID does not exist","apierror")); } @@ -198,9 +201,9 @@ exports.createGroupPad = function(groupID, padName, text, callback) padManager.doesPadExists(padID, function(err, exists) { if(ERR(err, callback)) return; - + //pad exists already - if(exists == true) + if(exists) { callback(new customError("padName does already exist","apierror")); } @@ -231,16 +234,16 @@ exports.createGroupPad = function(groupID, padName, text, callback) 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) + if(!exists) { callback(new customError("groupID does not exist","apierror")); } @@ -254,12 +257,12 @@ exports.listPads = function(groupID, callback) }); } }); -} +}; /** * Generates a random String with the given length. Is needed to generate the Author Ids */ -function randomString(len) +function randomString(len) { var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; var randomstring = ''; diff --git a/node/db/Pad.js b/node/db/Pad.js index 7807d4643..10c5f7af1 100644 --- a/node/db/Pad.js +++ b/node/db/Pad.js @@ -2,8 +2,6 @@ * The pad object, defined with joose */ -require('joose'); - var ERR = require("async-stacktrace"); var Changeset = require("../utils/Changeset"); var AttributePoolFactory = require("../utils/AttributePoolFactory"); @@ -15,497 +13,463 @@ var padManager = require("./PadManager"); var padMessageHandler = require("../handler/PadMessageHandler"); var readOnlyManager = require("./ReadOnlyManager"); var crypto = require("crypto"); +var cleanText = require("../utils/cleantext").cleanText; -/** - * Copied from the Etherpad source code. It converts Windows line breaks to Unix line breaks and convert Tabs to spaces - * @param txt - */ -exports.cleanText = function (txt) { - return txt.replace(/\r\n/g,'\n').replace(/\r/g,'\n').replace(/\t/g, ' ').replace(/\xa0/g, ' '); -} +var Pad = function Pad(id) { + this.atext = Changeset.makeAText("\n"); + this.pool = AttributePoolFactory.createAttributePool(); + this.head = -1; + this.chatHead = -1; + this.publicStatus = false; + this.passwordHash = null; + this.id = id; +}; -Class('Pad', { +exports.Pad = Pad; - // these are the properties - has : { - - atext : { - is : 'rw', // readwrite - init : function() { return Changeset.makeAText("\n"); } // first value - }, // atext - - pool : { - is: 'rw', - init : function() { return AttributePoolFactory.createAttributePool(); }, - getterName : 'apool' // legacy - }, // pool - - head : { - is : 'rw', - init : -1, - getterName : 'getHeadRevisionNumber' - }, // head - - chatHead : { - is: 'rw', - init: -1 - }, // chatHead - - publicStatus : { - is: 'rw', - init: false, - getterName : 'getPublicStatus' - }, //publicStatus - - passwordHash : { - is: 'rw', - init: null - }, // passwordHash - - id : { is : 'r' } - }, +Pad.prototype.apool = function apool() { + return this.pool; +}; - methods : { - - BUILD : function (id) +Pad.prototype.getHeadRevisionNumber = function getHeadRevisionNumber() { + return this.head; +}; + +Pad.prototype.getPublicStatus = function getPublicStatus() { + return this.publicStatus; +}; + +Pad.prototype.appendRevision = function appendRevision(aChangeset, author) { + + if(!author) + { + author = ''; + } + + var newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool); + Changeset.copyAText(newAText, this.atext); + + var newRev = ++this.head; + + var newRevData = {}; + newRevData.changeset = aChangeset; + newRevData.meta = {}; + newRevData.meta.author = author; + newRevData.meta.timestamp = new Date().getTime(); + + //ex. getNumForAuthor + if(author !== '') + { + this.pool.putAttrib(['author', author || '']); + } + + if(newRev % 100 === 0) + { + newRevData.meta.atext = this.atext; + } + + db.set("pad:"+this.id+":revs:"+newRev, newRevData); + db.set("pad:"+this.id, {atext: this.atext, + pool: this.pool.toJsonable(), + head: this.head, + chatHead: this.chatHead, + publicStatus: this.publicStatus, + passwordHash: this.passwordHash}); + + +}; + +Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum, 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); +}; + +Pad.prototype.getRevisionDate = function getRevisionDate(revNum, 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] !== "") { - return { - 'id' : id, - } - }, - - appendRevision : function(aChangeset, author) - { - if(!author) - author = ''; + authors.push(this.pool.numToAttrib[key][1]); + } + } - var newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool); - Changeset.copyAText(newAText, this.atext); - - var newRev = ++this.head; - - var newRevData = {}; - newRevData.changeset = aChangeset; - newRevData.meta = {}; - newRevData.meta.author = author; - newRevData.meta.timestamp = new Date().getTime(); - - //ex. getNumForAuthor - if(author != '') - this.pool.putAttrib(['author', author || '']); - - if(newRev % 100 == 0) - { - newRevData.meta.atext = this.atext; - } - - db.set("pad:"+this.id+":revs:"+newRev, newRevData); - db.set("pad:"+this.id, {atext: this.atext, - pool: this.pool.toJsonable(), - head: this.head, - chatHead: this.chatHead, - publicStatus: this.publicStatus, - passwordHash: this.passwordHash}); - }, //appendRevision - - getRevisionChangeset : function(revNum, callback) + return authors; +}; + +Pad.prototype.getInternalRevisionAText = function getInternalRevisionAText(targetRev, callback) { + var _this = this; + + var keyRev = this.getKeyRevisionNumber(targetRev); + var atext; + var changesets = []; + + //find out which changesets are needed + var neededChangesets = []; + var curRev = keyRev; + while (curRev < targetRev) + { + curRev++; + neededChangesets.push(curRev); + } + + async.series([ + //get all needed data out of the database + function(callback) { - db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback); - }, // getRevisionChangeset - - getRevisionAuthor : function(revNum, callback) - { - db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "author"], callback); - }, // getRevisionAuthor - - getRevisionDate : function(revNum, callback) - { - db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "timestamp"], callback); - }, // getRevisionAuthor - - getAllAuthors : function() - { - var authors = []; - - for(key in this.pool.numToAttrib) - { - if(this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "") + async.parallel([ + //get the atext of the key revision + function (callback) { - authors.push(this.pool.numToAttrib[key][1]); + db.getSub("pad:"+_this.id+":revs:"+keyRev, ["meta", "atext"], function(err, _atext) + { + if(ERR(err, callback)) return; + atext = Changeset.cloneAText(_atext); + callback(); + }); + }, + //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(); + }); + }, callback); } - } - - return authors; + ], callback); }, - - getInternalRevisionAText : function(targetRev, callback) + //apply all changesets to the key changeset + function(callback) { - var _this = this; - - var keyRev = this.getKeyRevisionNumber(targetRev); - var atext; - var changesets = []; - - //find out which changesets are needed - var neededChangesets = []; + var apool = _this.apool(); var curRev = keyRev; - while (curRev < targetRev) + + while (curRev < targetRev) { curRev++; - neededChangesets.push(curRev); + var cs = changesets[curRev]; + atext = Changeset.applyToAText(cs, atext, apool); } - - async.series([ - //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; - atext = Changeset.cloneAText(_atext); - callback(); - }); - }, - //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(); - }); - }, callback); - } - ], callback); - }, - //apply all changesets to the key changeset - function(callback) - { - var apool = _this.apool(); - var curRev = keyRev; - - while (curRev < targetRev) - { - curRev++; - var cs = changesets[curRev]; - atext = Changeset.applyToAText(cs, atext, apool); - } - - callback(null); - } - ], function(err) + + callback(null); + } + ], function(err) + { + if(ERR(err, callback)) return; + callback(null, atext); + }); + +}; + +Pad.prototype.getKeyRevisionNumber = function getKeyRevisionNumber(revNum) { + return Math.floor(revNum / 100) * 100; +}; + +Pad.prototype.text = function text() { + return this.atext.text; +}; + +Pad.prototype.setText = function setText(newText) { + //clean the new text + newText = cleanText(newText); + + var oldText = this.text(); + + //create the changeset + var changeset = Changeset.makeSplice(oldText, 0, oldText.length-1, newText); + + //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 new chat head + db.setSub("pad:"+this.id, ["chatHead"], this.chatHead); +}; + +Pad.prototype.getChatMessage = function getChatMessage(entryNum, callback) { + var _this = this; + 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; - callback(null, atext); + entry = _entry; + callback(); }); }, - - getKeyRevisionNumber : function(revNum) + //add the authorName + function(callback) { - return Math.floor(revNum / 100) * 100; - }, - - text : function() - { - return this.atext.text; - }, - - setText : function(newText) - { - //clean the new text - newText = exports.cleanText(newText); - - var oldText = this.text(); - - //create the changeset - var changeset = Changeset.makeSplice(oldText, 0, oldText.length-1, newText); - - //append the changeset - this.appendRevision(changeset); - }, - - appendChatMessage: function(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 new chat head - db.setSub("pad:"+this.id, ["chatHead"], this.chatHead); - }, - - getChatMessage: function(entryNum, callback) - { - var _this = this; - 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; - entry = _entry; - callback(); - }); - }, - //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; - entry.userName = authorName; - callback(); - }); - } - ], function(err) + //this chat message doesn't exist, return null + if(!entry) { - if(ERR(err, callback)) return; - callback(null, entry); - }); - }, - - getLastChatMessages: function(count, callback) - { - //return an empty array if there are no chat messages - if(this.chatHead == -1) - { - callback(null, []); + callback(); return; } - - var _this = this; - - //works only if we decrement the amount, for some reason - count--; - //set the startpoint - var start = this.chatHead-count; - if(start < 0) - start = 0; - - //set the endpoint - var end = this.chatHead; - - //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}); - order++; - } - - //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; - entries[entryObject.order] = entry; - callback(); - }); - }, 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 - var cleanedEntries = []; - for(var i=0;i delete the entry of this pad in the group - function(callback) - { - //is it a group pad? - if(padID.indexOf("$")!=-1) - { - var groupID = padID.substring(0,padID.indexOf("$")); - - db.get("group:" + groupID, function (err, group) - { - if(ERR(err, callback)) return; - - //remove the pad entry - delete group.pads[padID]; - - //set the new value - db.set("group:" + groupID, group); - - callback(); - }); - } - //its no group pad, nothing to do here - else - { - callback(); - } - }, - //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); - - callback(); - }); - }, - //delete all chat messages - function(callback) - { - var chatHead = _this.chatHead; - - for(var i=0;i<=chatHead;i++) - { - db.remove("pad:"+padID+":chat:"+i); - } - - callback(); - }, - //delete all revisions - function(callback) - { - var revHead = _this.head; - - for(var i=0;i<=revHead;i++) - { - db.remove("pad:"+padID+":revs:"+i); - } - - callback(); - } - ], callback); - }, - //delete the pad entry and delete pad from padManager - function(callback) - { - db.remove("pad:"+padID); - padManager.unloadPad(padID); - callback(); - } - ], function(err) + //get the authorName + authorManager.getAuthorName(entry.userId, function(err, authorName) { if(ERR(err, callback)) return; + entry.userName = authorName; callback(); - }) - }, - //set in db - setPublicStatus: function(publicStatus) - { - this.publicStatus = publicStatus; - db.setSub("pad:"+this.id, ["publicStatus"], this.publicStatus); - }, - setPassword: function(password) - { - this.passwordHash = password == null ? null : hash(password, generateSalt()); - db.setSub("pad:"+this.id, ["passwordHash"], this.passwordHash); - }, - isCorrectPassword: function(password) - { - return compare(this.passwordHash, password) - }, - isPasswordProtected: function() - { - return this.passwordHash != null; + }); } - }, // methods -}); + ], function(err) + { + if(ERR(err, callback)) return; + callback(null, entry); + }); +}; + +Pad.prototype.getLastChatMessages = function getLastChatMessages(count, callback) { + //return an empty array if there are no chat messages + if(this.chatHead == -1) + { + callback(null, []); + return; + } + + var _this = this; + + //works only if we decrement the amount, for some reason + count--; + + //set the startpoint + var start = this.chatHead-count; + if(start < 0) + start = 0; + + //set the endpoint + var end = this.chatHead; + + //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}); + order++; + } + + //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; + entries[entryObject.order] = entry; + callback(); + }); + }, 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 + var cleanedEntries = []; + for(var i=0;i delete the entry of this pad in the group + function(callback) + { + //is it a group pad? + if(padID.indexOf("$")!=-1) + { + var groupID = padID.substring(0,padID.indexOf("$")); + + db.get("group:" + groupID, function (err, group) + { + if(ERR(err, callback)) return; + + //remove the pad entry + delete group.pads[padID]; + + //set the new value + db.set("group:" + groupID, group); + + callback(); + }); + } + //its no group pad, nothing to do here + else + { + callback(); + } + }, + //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); + + callback(); + }); + }, + //delete all chat messages + function(callback) + { + var chatHead = _this.chatHead; + + for(var i=0;i<=chatHead;i++) + { + db.remove("pad:"+padID+":chat:"+i); + } + + callback(); + }, + //delete all revisions + function(callback) + { + var revHead = _this.head; + + for(var i=0;i<=revHead;i++) + { + db.remove("pad:"+padID+":revs:"+i); + } + + callback(); + } + ], callback); + }, + //delete the pad entry and delete pad from padManager + function(callback) + { + db.remove("pad:"+padID); + padManager.unloadPad(padID); + callback(); + } + ], function(err) + { + if(ERR(err, callback)) return; + callback(); + }); +}; + +//set in db +Pad.prototype.setPublicStatus = function setPublicStatus(publicStatus) { + this.publicStatus = publicStatus; + db.setSub("pad:"+this.id, ["publicStatus"], this.publicStatus); +}; + +Pad.prototype.setPassword = function setPassword(password) { + this.passwordHash = password ? hash(password, generateSalt()) : null; + db.setSub("pad:"+this.id, ["passwordHash"], this.passwordHash); +}; + +Pad.isCorrectPassword = function isCorrectPassword(password) { + return compare(this.passwordHash, password); +}; + +Pad.isPasswordProtected = function isPasswordProtected() { + return this.passwordHash ? true: false; +}; + /* Crypto helper methods */ @@ -531,5 +495,5 @@ function generateSalt() function compare(hashStr, password) { - return hash(password, hashStr.split("$")[1]) === hashStr; + return hash(password, hashStr.split("$")[1]) === hashStr; } diff --git a/node/db/PadManager.js b/node/db/PadManager.js index 36c272637..1c9744e91 100644 --- a/node/db/PadManager.js +++ b/node/db/PadManager.js @@ -20,10 +20,10 @@ var ERR = require("async-stacktrace"); var customError = require("../utils/customError"); -require("../db/Pad"); +var Pad = require("../db/Pad").Pad; var db = require("./DB").db; -/** +/** * An Object containing all known Pads. Provides "get" and "set" functions, * which should be used instead of indexing with brackets. These prepend a * colon to the key, to avoid conflicting with built-in Object methods or with @@ -41,26 +41,26 @@ var globalPads = { /** * 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")); return; } - + //make text an optional parameter - if(typeof text == "function") + if(typeof text === "function") { callback = text; text = null; } - + //check if this is a valid text - if(text != null) + if(text) { //check if text is a string if(typeof text != "string") @@ -68,7 +68,7 @@ exports.getPad = function(id, text, callback) callback(new customError("text is not a string","apierror")); return; } - + //check if text is less than 100k chars if(text.length > 100000) { @@ -76,11 +76,11 @@ exports.getPad = function(id, text, callback) return; } } - + var pad = globalPads.get(id); - + //return pad if its already loaded - if(pad != null) + if(pad) { callback(null, pad); } @@ -88,17 +88,17 @@ exports.getPad = function(id, text, callback) else { pad = new Pad(id); - + //initalize the pad pad.init(text, function(err) { if(ERR(err, callback)) return; - + globalPads.set(id, pad); callback(null, pad); }); } -} +}; //checks if a pad exists exports.doesPadExists = function(padId, callback) @@ -106,18 +106,18 @@ exports.doesPadExists = function(padId, callback) db.get("pad:"+padId, function(err, value) { if(ERR(err, callback)) return; - callback(null, value != null); + callback(null, value ? true : false); }); -} +}; exports.isValidPadId = function(padId) { - return /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId); -} + return (/^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/).test(padId); +}; //removes a pad from the array exports.unloadPad = function(padId) { if(globalPads.get(padId)) globalPads.remove(padId); -} +}; diff --git a/node/db/ReadOnlyManager.js b/node/db/ReadOnlyManager.js index 73b3be9ec..d574bf296 100644 --- a/node/db/ReadOnlyManager.js +++ b/node/db/ReadOnlyManager.js @@ -27,9 +27,9 @@ var async = require("async"); * @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) @@ -39,10 +39,10 @@ exports.getReadOnlyId = function (padId, callback) function(dbReadOnlyId, callback) { //there is no readOnly Entry in the database, let's create one - if(dbReadOnlyId == null) + if(!dbReadOnlyId) { readOnlyId = "r." + randomString(16); - + db.set("pad2readonly:" + padId, readOnlyId); db.set("readonly2pad:" + readOnlyId, padId); } @@ -51,7 +51,7 @@ exports.getReadOnlyId = function (padId, callback) { readOnlyId = dbReadOnlyId; } - + callback(); } ], function(err) @@ -59,8 +59,8 @@ exports.getReadOnlyId = function (padId, callback) if(ERR(err, callback)) return; //return the results callback(null, readOnlyId); - }) -} + }); +}; /** * returns a the padId for a read only id @@ -69,12 +69,12 @@ exports.getReadOnlyId = function (padId, callback) exports.getPadId = function(readOnlyId, callback) { db.get("readonly2pad:" + readOnlyId, callback); -} +}; /** * Generates a random String with the given length. Is needed to generate the read only ids */ -function randomString(len) +function randomString(len) { var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; var randomstring = ''; diff --git a/node/db/SecurityManager.js b/node/db/SecurityManager.js index 52d5afcbe..a6784b7ec 100644 --- a/node/db/SecurityManager.js +++ b/node/db/SecurityManager.js @@ -24,18 +24,18 @@ var async = require("async"); var authorManager = require("./AuthorManager"); var padManager = require("./PadManager"); var sessionManager = require("./SessionManager"); -var settings = require("../utils/Settings") +var settings = require("../utils/Settings"); /** * This function controlls the access to a pad, it checks if the user can access a pad. * @param padID the pad the user wants to access * @param sesssionID 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, sessionID, token, password, callback) -{ +{ var statusObject; // a valid session is required (api-only mode) @@ -58,7 +58,7 @@ exports.checkAccess = function (padID, sessionID, token, password, callback) 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 @@ -68,7 +68,7 @@ exports.checkAccess = function (padID, sessionID, token, password, callback) 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"; // grant or deny access, with author of token @@ -81,13 +81,13 @@ exports.checkAccess = function (padID, sessionID, token, password, callback) // grant access, with author of token callback(null, statusObject); } - }) - + }); + //don't continue return; } } - + var groupID = padID.split("$")[0]; var padExists = false; var validSession = false; @@ -95,10 +95,10 @@ exports.checkAccess = function (padID, sessionID, token, password, callback) var tokenAuthor; var isPublic; var isPasswordProtected; - var passwordStatus = password == null ? "notGiven" : "wrong"; // notGiven, correct, wrong + var passwordStatus = password === null ? "notGiven" : "wrong"; // notGiven, correct, wrong async.series([ - //get basic informations from the database + //get basic informations from the database function(callback) { async.parallel([ @@ -123,19 +123,19 @@ exports.checkAccess = function (padID, sessionID, token, password, callback) callback(); return; } - + if(ERR(err, callback)) return; - + var now = Math.floor(new Date().getTime()/1000); - + //is it for this group? and is validUntil still ok? --> validSession if(sessionInfo.groupID == groupID && sessionInfo.validUntil > now) { validSession = true; } - + sessionAuthor = sessionInfo.authorID; - + callback(); }); }, @@ -156,28 +156,28 @@ exports.checkAccess = function (padID, sessionID, token, password, callback) function(callback) { //skip this if the pad doesn't exists - if(padExists == false) + if(!padExists) { callback(); return; } - + padManager.getPad(padID, function(err, pad) { if(ERR(err, callback)) return; - + //is it a public pad? isPublic = pad.getPublicStatus(); - + //is it password protected? isPasswordProtected = pad.isPasswordProtected(); - + //is password correct? if(isPasswordProtected && password && pad.isCorrectPassword(password)) { passwordStatus = "correct"; } - + callback(); }); }, @@ -214,7 +214,7 @@ exports.checkAccess = function (padID, sessionID, token, password, callback) { throw new Error("Ops, something wrong happend"); } - } + } //- a valid session for this group avaible but pad doesn't exists else if(validSession && !padExists) { @@ -238,7 +238,7 @@ exports.checkAccess = function (padID, sessionID, token, password, callback) //--> grant access, with author of token statusObject = {accessStatus: "grant", authorID: tokenAuthor}; } - //- its public and the pad is password protected but wrong password given + //- 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 @@ -260,14 +260,14 @@ exports.checkAccess = function (padID, sessionID, token, password, callback) { throw new Error("Ops, something wrong happend"); } - } + } // there is no valid session avaiable AND pad doesn't exists else { //--> deny access statusObject = {accessStatus: "deny"}; } - + callback(); } ], function(err) @@ -275,4 +275,4 @@ exports.checkAccess = function (padID, sessionID, token, password, callback) if(ERR(err, callback)) return; callback(null, statusObject); }); -} +}; diff --git a/node/db/SessionManager.js b/node/db/SessionManager.js index a394f5442..e98100c67 100644 --- a/node/db/SessionManager.js +++ b/node/db/SessionManager.js @@ -17,24 +17,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + var ERR = require("async-stacktrace"); var customError = require("../utils/customError"); 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 db.get("session:" + sessionID, function (err, session) { if(ERR(err, callback)) return; - callback(null, session != null); + callback(null, session ? true : false); }); -} - +}; + /** * Creates a new session between an author and a group */ @@ -49,9 +49,9 @@ exports.createSession = function(groupID, authorID, validUntil, callback) groupMangager.doesGroupExist(groupID, function(err, exists) { if(ERR(err, callback)) return; - + //group does not exist - if(exists == false) + if(!exists) { callback(new customError("groupID does not exist","apierror")); } @@ -68,9 +68,9 @@ exports.createSession = function(groupID, authorID, validUntil, callback) authorMangager.doesAuthorExists(authorID, function(err, exists) { if(ERR(err, callback)) return; - + //author does not exist - if(exists == false) + if(!exists) { callback(new customError("authorID does not exist","apierror")); } @@ -88,9 +88,9 @@ exports.createSession = function(groupID, authorID, validUntil, callback) if(typeof validUntil != "number") { //try to parse the number - if(!isNaN(parseInt(validUntil))) + if(!isNaN(parseInt(validUntil, 10))) { - validUntil = parseInt(validUntil); + validUntil = parseInt(validUntil, 10); } else { @@ -98,34 +98,34 @@ exports.createSession = function(groupID, authorID, validUntil, callback) return; } } - + //ensure this is not a negativ 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")); return; } - + //check if validUntil is in the future if(Math.floor(new Date().getTime()/1000) > validUntil) { callback(new customError("validUntil is in the past","apierror")); return; } - + //generate sessionID sessionID = "s." + randomString(16); - + //set the session into the database db.set("session:" + sessionID, {"groupID": groupID, "authorID": authorID, "validUntil": validUntil}); - + callback(); }, //set the group2sessions entry @@ -135,19 +135,19 @@ exports.createSession = function(groupID, authorID, validUntil, callback) 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) + if(!group2sessions) { group2sessions = {sessionIDs : {}}; } - + //add the entry for this session group2sessions.sessionIDs[sessionID] = 1; - + //save the new element back db.set("group2sessions:" + groupID, group2sessions); - + callback(); }); }, @@ -158,30 +158,30 @@ exports.createSession = function(groupID, authorID, validUntil, callback) 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) + if(!author2sessions) { author2sessions = {sessionIDs : {}}; } - + //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 callback(null, {sessionID: sessionID}); - }) -} + }); +}; exports.getSessionInfo = function(sessionID, callback) { @@ -189,11 +189,11 @@ exports.getSessionInfo = function(sessionID, callback) db.get("session:" + sessionID, function (err, session) { if(ERR(err, callback)) return; - + //session does not exists - if(session == null) + if(session === null) { - callback(new customError("sessionID does not exist","apierror")) + callback(new customError("sessionID does not exist","apierror")); } //everything is fine, return the sessioninfos else @@ -201,7 +201,7 @@ exports.getSessionInfo = function(sessionID, callback) callback(null, session); } }); -} +}; /** * Deletes a session @@ -218,18 +218,18 @@ exports.deleteSession = function(sessionID, callback) db.get("session:" + sessionID, function (err, session) { if(ERR(err, callback)) return; - + //session does not exists - if(session == null) + if(!session) { - callback(new customError("sessionID does not exist","apierror")) + callback(new customError("sessionID does not exist","apierror")); } //everything is fine, return the sessioninfos else { authorID = session.authorID; groupID = session.groupID; - + callback(); } }); @@ -259,32 +259,32 @@ exports.deleteSession = function(sessionID, callback) { //remove the session db.remove("session:" + sessionID); - + //remove session from group2sessions delete group2sessions.sessionIDs[sessionID]; db.set("group2sessions:" + groupID, group2sessions); - + //remove session from author2sessions delete author2sessions.sessionIDs[sessionID]; db.set("author2sessions:" + authorID, author2sessions); - + callback(); } ], function(err) { if(ERR(err, callback)) return; callback(); - }) -} + }); +}; exports.listSessionsOfGroup = function(groupID, callback) { groupMangager.doesGroupExist(groupID, function(err, exists) { if(ERR(err, callback)) return; - + //group does not exist - if(exists == false) + if(!exists) { callback(new customError("groupID does not exist","apierror")); } @@ -294,16 +294,16 @@ exports.listSessionsOfGroup = function(groupID, callback) 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) + if(!exists) { callback(new customError("authorID does not exist","apierror")); } @@ -313,7 +313,7 @@ exports.listSessionsOfAuthor = function(authorID, callback) listSessionsWithDBKey("author2sessions:" + authorID, callback); } }); -} +}; //this function is basicly the code listSessionsOfAuthor and listSessionsOfGroup has in common function listSessionsWithDBKey (dbkey, callback) @@ -332,14 +332,14 @@ function listSessionsWithDBKey (dbkey, callback) }); }, function(callback) - { + { //collect all sessionIDs in an arrary var sessionIDs = []; for (var i in sessions) { sessionIDs.push(i); } - + //foreach trough the sessions and get the sessioninfos async.forEach(sessionIDs, function(sessionID, callback) { @@ -361,7 +361,7 @@ function listSessionsWithDBKey (dbkey, callback) /** * Generates a random String with the given length. Is needed to generate the SessionIDs */ -function randomString(len) +function randomString(len) { var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; var randomstring = ''; @@ -375,6 +375,6 @@ function randomString(len) //checks if a number is an int function is_int(value) -{ - return (parseFloat(value) == parseInt(value)) && !isNaN(value) +{ + return (parseFloat(value) == parseInt(value, 10)) && !isNaN(value); } diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index 5c29f9e4c..cd29d3b00 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -28,7 +28,7 @@ try { apikey = fs.readFileSync("../APIKEY.txt","utf8"); } -catch(e) +catch(e) { apikey = randomString(32); fs.writeFileSync("../APIKEY.txt",apikey,"utf8"); @@ -37,28 +37,28 @@ catch(e) //a list of all functions var functions = { "createGroup" : [], - "createGroupIfNotExistsFor" : ["groupMapper"], - "deleteGroup" : ["groupID"], - "listPads" : ["groupID"], - "createPad" : ["padID", "text"], + "createGroupIfNotExistsFor" : ["groupMapper"], + "deleteGroup" : ["groupID"], + "listPads" : ["groupID"], + "createPad" : ["padID", "text"], "createGroupPad" : ["groupID", "padName", "text"], - "createAuthor" : ["name"], - "createAuthorIfNotExistsFor": ["authorMapper" , "name"], - "createSession" : ["groupID", "authorID", "validUntil"], - "deleteSession" : ["sessionID"], - "getSessionInfo" : ["sessionID"], - "listSessionsOfGroup" : ["groupID"], - "listSessionsOfAuthor" : ["authorID"], + "createAuthor" : ["name"], + "createAuthorIfNotExistsFor": ["authorMapper" , "name"], + "createSession" : ["groupID", "authorID", "validUntil"], + "deleteSession" : ["sessionID"], + "getSessionInfo" : ["sessionID"], + "listSessionsOfGroup" : ["groupID"], + "listSessionsOfAuthor" : ["authorID"], "getText" : ["padID", "rev"], "setText" : ["padID", "text"], "getHTML" : ["padID", "rev"], "setHTML" : ["padID", "html"], - "getRevisionsCount" : ["padID"], - "deletePad" : ["padID"], + "getRevisionsCount" : ["padID"], + "deletePad" : ["padID"], "getReadOnlyID" : ["padID"], - "setPublicStatus" : ["padID", "publicStatus"], - "getPublicStatus" : ["padID"], - "setPassword" : ["padID", "password"], + "setPublicStatus" : ["padID", "publicStatus"], + "getPublicStatus" : ["padID"], + "setPassword" : ["padID", "password"], "isPasswordProtected" : ["padID"] }; @@ -72,12 +72,12 @@ var functions = { exports.handle = function(functionName, fields, req, res) { //check the api key! - if(fields["apikey"] != apikey.trim()) + if(fields.apikey != apikey.trim()) { res.send({code: 4, message: "no or wrong API Key", data: null}); return; } - + //check if this is a valid function name var isKnownFunctionname = false; for(var knownFunctionname in functions) @@ -88,30 +88,30 @@ exports.handle = function(functionName, fields, req, res) break; } } - + //say goodbye if this is a unkown function if(!isKnownFunctionname) { res.send({code: 3, message: "no such function", data: null}); return; } - + //put the function parameters in an array var functionParams = []; for(var i=0;i -1) { tempDirectory = process.env.TEMP; } - + /** * do a requested export - */ + */ exports.doExport = function(req, res, padId, type) { //tell the browser that this is a downloadable file res.attachment(padId + "." + type); + var randNum; + var srcFile; + var destFile; + //if this is a plain text export, we can do this directly if(type == "txt") { padManager.getPad(padId, function(err, pad) { ERR(err); - + res.send(pad.text()); }); } else if(type == 'dokuwiki') { - var randNum; - var srcFile, destFile; async.series([ //render the dokuwiki document @@ -71,7 +75,7 @@ exports.doExport = function(req, res, padId, type) res.send(dokuwiki); callback("stop"); }); - }, + } ], function(err) { if(err && err != "stop") throw err; @@ -80,8 +84,6 @@ exports.doExport = function(req, res, padId, type) else { var html; - var randNum; - var srcFile, destFile; async.series([ //render the html document @@ -92,7 +94,7 @@ exports.doExport = function(req, res, padId, type) if(ERR(err, callback)) return; html = _html; callback(); - }); + }); }, //decide what to do with the html export function(callback) @@ -101,13 +103,13 @@ exports.doExport = function(req, res, padId, type) if(type == "html") { res.send(html); - callback("stop"); + callback("stop"); } else //write the html export to a file { randNum = Math.floor(Math.random()*0xFFFFFFFF); srcFile = tempDirectory + "/eplite_export_" + randNum + ".html"; - fs.writeFile(srcFile, html, callback); + fs.writeFile(srcFile, html, callback); } }, //send the convert job to abiword @@ -115,7 +117,7 @@ exports.doExport = function(req, res, padId, type) { //ensure html can be collected by the garbage collector html = null; - + destFile = tempDirectory + "/eplite_export_" + randNum + "." + type; abiword.convertFile(srcFile, destFile, type, callback); }, @@ -137,7 +139,7 @@ exports.doExport = function(req, res, padId, type) //100ms delay to accomidate for slow windows fs if(os.type().indexOf("Windows") > -1) { - setTimeout(function() + setTimeout(function() { fs.unlink(destFile, callback); }, 100); @@ -152,6 +154,6 @@ exports.doExport = function(req, res, padId, type) ], function(err) { if(err && err != "stop") ERR(err); - }) + }); } }; diff --git a/node/handler/ImportHandler.js b/node/handler/ImportHandler.js index 06a2ce5ca..f96075955 100644 --- a/node/handler/ImportHandler.js +++ b/node/handler/ImportHandler.js @@ -28,8 +28,10 @@ var formidable = require('formidable'); var os = require("os"); //load abiword only if its enabled -if(settings.abiword != null) +if(settings.abiword) +{ var abiword = require("../utils/Abiword"); +} var tempDirectory = "/tmp/"; @@ -38,20 +40,20 @@ if(os.type().indexOf("Windows") > -1) { tempDirectory = process.env.TEMP; } - + /** * do a requested import - */ + */ exports.doImport = function(req, res, padId) { //pipe to a file //convert file to text via abiword //set text in the pad - + var srcFile, destFile; var pad; var text; - + async.series([ //save the uploaded file to /tmp function(callback) @@ -59,9 +61,9 @@ exports.doImport = function(req, res, padId) var form = new formidable.IncomingForm(); form.keepExtensions = true; form.uploadDir = tempDirectory; - - form.parse(req, function(err, fields, files) - { + + form.parse(req, function(err, fields, files) + { //the upload failed, stop at this point if(err || files.file === undefined) { @@ -69,7 +71,7 @@ exports.doImport = function(req, res, padId) callback("uploadFailed"); } //everything ok, continue - else + else { //save the path of the uploaded file srcFile = files.file.path; @@ -77,14 +79,14 @@ exports.doImport = function(req, res, padId) } }); }, - + //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 = srcFile.split(".")[1].toLowerCase(); var knownFileEndings = ["txt", "doc", "docx", "pdf", "odt", "html", "htm"]; - + //find out if this is a known file ending var fileEndingKnown = false; for(var i in knownFileEndings) @@ -94,7 +96,7 @@ exports.doImport = function(req, res, padId) fileEndingKnown = true; } } - + //if the file ending is known, continue as normal if(fileEndingKnown) { @@ -105,11 +107,11 @@ exports.doImport = function(req, res, padId) { var oldSrcFile = srcFile; srcFile = srcFile.split(".")[0] + ".txt"; - + fs.rename(oldSrcFile, srcFile, callback); } }, - + //convert file to text function(callback) { @@ -117,7 +119,7 @@ exports.doImport = function(req, res, padId) destFile = tempDirectory + "eplite_import_" + randNum + ".txt"; abiword.convertFile(srcFile, destFile, "txt", callback); }, - + //get the pad object function(callback) { @@ -128,7 +130,7 @@ exports.doImport = function(req, res, padId) callback(); }); }, - + //read the text function(callback) { @@ -136,30 +138,30 @@ exports.doImport = function(req, res, padId) { if(ERR(err, callback)) return; text = _text; - - //node on windows has a delay on releasing of the file lock. + + //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) - { + if(os.type().indexOf("Windows") > -1) + { setTimeout(function() { callback(); }, 100); - } - else - { - callback(); - } + } + else + { + callback(); + } }); }, - + //change text of the pad and broadcast the changeset function(callback) { pad.setText(text); padMessageHandler.updatePadClients(pad, callback); }, - + //clean up temporary files function(callback) { @@ -184,8 +186,8 @@ exports.doImport = function(req, res, padId) } ERR(err); - + //close the connection res.send("ok"); }); -} +}; diff --git a/node/handler/PadMessageHandler.js b/node/handler/PadMessageHandler.js index d86528498..45bb90eff 100644 --- a/node/handler/PadMessageHandler.js +++ b/node/handler/PadMessageHandler.js @@ -1,6 +1,6 @@ /** - * The MessageHandler handles all Messages that comes from Socket.IO and controls the sessions - */ + * The MessageHandler handles all Messages that comes from Socket.IO and controls the sessions + */ /* * Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka (Primary Technology Ltd) @@ -61,18 +61,18 @@ var socketio; exports.setSocketIO = function(socket_io) { socketio=socket_io; -} +}; /** * Handles the connection of a new user * @param client the new client */ exports.handleConnect = function(client) -{ +{ //Initalize session2pad and sessioninfos for this new session - session2pad[client.id]=null; + session2pad[client.id]=null; sessioninfos[client.id]={}; -} +}; /** * Kicks all sessions from a pad @@ -89,27 +89,27 @@ exports.kickSessionsFromPad = function(padID) { socketio.sockets.sockets[pad2sessions[padID][i]].json.send({disconnect:"deleted"}); } -} +}; /** * Handles the disconnection of a user * @param client the client that leaves */ exports.handleDisconnect = function(client) -{ +{ //save the padname of this session var sessionPad=session2pad[client.id]; - + //if this connection was already etablished with a handshake, send a disconnect message to the others if(sessioninfos[client.id] && sessioninfos[client.id].author) { var author = sessioninfos[client.id].author; - + //get the author color out of the db authorManager.getAuthorColorId(author, function(err, color) { ERR(err); - + //prepare the notification for the other users on the pad, that this user left var messageToTheOtherUsers = { "type": "COLLABROOM", @@ -123,29 +123,29 @@ exports.handleDisconnect = function(client) } } }; - + //Go trough all user that are still on the pad, and send them the USER_LEAVE message - for(i in pad2sessions[sessionPad]) + for(var i in pad2sessions[sessionPad]) { socketio.sockets.sockets[pad2sessions[sessionPad][i]].json.send(messageToTheOtherUsers); } - }); + }); } - + //Go trough all sessions of this pad, search and destroy the entry of this client - for(i in pad2sessions[sessionPad]) + for(var i in pad2sessions[sessionPad]) { if(pad2sessions[sessionPad][i] == client.id) { - pad2sessions[sessionPad].splice(i, 1); + pad2sessions[sessionPad].splice(i, 1); break; } } - + //Delete the session2pad and sessioninfos entrys of this session - delete session2pad[client.id]; - delete sessioninfos[client.id]; -} + delete session2pad[client.id]; + delete sessioninfos[client.id]; +}; /** * Handles a message from a user @@ -153,8 +153,8 @@ exports.handleDisconnect = function(client) * @param message the message from the client */ exports.handleMessage = function(client, message) -{ - if(message == null) +{ + if(!message) { messageLogger.warn("Message is null!"); return; @@ -164,39 +164,50 @@ exports.handleMessage = function(client, message) messageLogger.warn("Message has no type attribute!"); return; } - + //Check what type of message we get and delegate to the other methodes if(message.type == "CLIENT_READY") { handleClientReady(client, message); } - else if(message.type == "COLLABROOM" && + else if(message.type == "COLLABROOM" && message.data.type == "USER_CHANGES") { handleUserChanges(client, message); } - else if(message.type == "COLLABROOM" && + else if(message.type == "COLLABROOM" && message.data.type == "USERINFO_UPDATE") { handleUserInfoUpdate(client, message); } - else if(message.type == "COLLABROOM" && + else if(message.type == "COLLABROOM" && message.data.type == "CHAT_MESSAGE") { handleChatMessage(client, message); } - else if(message.type == "COLLABROOM" && - message.data.type == "CLIENT_MESSAGE" && - message.data.payload.type == "suggestUserName") + else if(message.type == "COLLABROOM" && + message.data.type == "CLIENT_MESSAGE") { - handleSuggestUserName(client, message); + if(message.data.payload.type == "suggestUserName") + { + handleSuggestUserName(client, message); + } + else if (message.data.payload.type == "padoptions") + { + messageLogger.info("Handler not implemented for payload type: " + message.data.payload.type); + //handlePadOptions + } + else + { + messageLogger.warn("Dropped COLLABROOM CLIENT_MESSAGE message, unknown Message Type " + message.type); + } } //if the message type is unknown, throw an exception else { messageLogger.warn("Dropped message, unknown Message Type " + message.type); } -} +}; /** * Handles a Chat Message @@ -209,10 +220,10 @@ function handleChatMessage(client, message) var userId = sessioninfos[client.id].author; var text = message.data.text; var padId = session2pad[client.id]; - + var pad; var userName; - + async.series([ //get the pad function(callback) @@ -238,7 +249,7 @@ function handleChatMessage(client, message) { //save the chat message pad.appendChatMessage(text, userId, time); - + var msg = { type: "COLLABROOM", data: { @@ -249,13 +260,13 @@ function handleChatMessage(client, message) text: text } }; - + //broadcast the chat message to everyone on the pad for(var i in pad2sessions[padId]) { socketio.sockets.sockets[pad2sessions[padId][i]].json.send(msg); } - + callback(); } ], function(err) @@ -273,19 +284,19 @@ function handleChatMessage(client, message) function handleSuggestUserName(client, message) { //check if all ok - if(message.data.payload.newName == null) + if(!message.data.payload.newName) { messageLogger.warn("Dropped message, suggestUserName Message has no newName!"); return; } - if(message.data.payload.unnamedId == null) + if(!message.data.payload.unnamedId) { messageLogger.warn("Dropped message, suggestUserName Message has no unnamedId!"); return; } - + var padId = session2pad[client.id]; - + //search the author and send him this message for(var i in pad2sessions[padId]) { @@ -305,30 +316,30 @@ function handleSuggestUserName(client, message) function handleUserInfoUpdate(client, message) { //check if all ok - if(message.data.userInfo.colorId == null) + if(!message.data.userInfo.colorId) { messageLogger.warn("Dropped message, USERINFO_UPDATE Message has no colorId!"); return; } - + //Find out the author name of this session var author = sessioninfos[client.id].author; - + //Tell the authorManager about the new attributes authorManager.setAuthorColorId(author, message.data.userInfo.colorId); authorManager.setAuthorName(author, message.data.userInfo.name); - + var padId = session2pad[client.id]; - + //set a null name, when there is no name set. cause the client wants it null - if(message.data.userInfo.name == null) + if(!message.data.userInfo.name) { message.data.userInfo.name = null; } - + //The Client don't know about a USERINFO_UPDATE, it can handle only new user_newinfo, so change the message type message.data.type = "USER_NEWINFO"; - + //Send the other clients on the pad the update message for(var i in pad2sessions[padId]) { @@ -349,29 +360,29 @@ function handleUserInfoUpdate(client, message) function handleUserChanges(client, message) { //check if all ok - if(message.data.baseRev == null) + if(message.data.baseRev === null || message.data.baseRev === undefined) { messageLogger.warn("Dropped message, USER_CHANGES Message has no baseRev!"); return; } - if(message.data.apool == null) + if(!message.data.apool) { messageLogger.warn("Dropped message, USER_CHANGES Message has no apool!"); return; } - if(message.data.changeset == null) + if(!message.data.changeset) { messageLogger.warn("Dropped message, USER_CHANGES Message has no changeset!"); return; } - + //get all Vars we need var baseRev = message.data.baseRev; var wireApool = (AttributePoolFactory.createAttributePool()).fromJsonable(message.data.apool); var changeset = message.data.changeset; - + var r, apool, pad; - + async.series([ //get the pad function(callback) @@ -387,13 +398,13 @@ function handleUserChanges(client, message) function(callback) { //ex. _checkChangesetAndPool - + //Copied from Etherpad, don't know what it does exactly try { //this looks like a changeset check, it throws errors sometimes Changeset.checkRep(changeset); - + Changeset.eachAttribNumber(changeset, function(n) { if (! wireApool.getAttrib(n)) { throw "Attribute pool is missing attribute "+n+" for changeset "+changeset; @@ -407,27 +418,27 @@ function handleUserChanges(client, message) client.json.send({disconnect:"badChangeset"}); return; } - + //ex. adoptChangesetAttribs - + //Afaik, it copies the new attributes from the changeset, to the global Attribute Pool changeset = Changeset.moveOpsToNewPool(changeset, wireApool, pad.pool); - + //ex. applyUserChanges apool = pad.pool; r = baseRev; - + //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; - + changeset = Changeset.follow(c, changeset, false, apool); callback(null); }); @@ -440,29 +451,29 @@ function handleUserChanges(client, message) function (callback) { var prevText = pad.text(); - - if (Changeset.oldLen(changeset) != prevText.length) + + if (Changeset.oldLen(changeset) != prevText.length) { console.warn("Can't apply USER_CHANGES "+changeset+" with oldLen " + Changeset.oldLen(changeset) + " to document of length " + prevText.length); client.json.send({disconnect:"badChangeset"}); callback(); return; } - + var thisAuthor = sessioninfos[client.id].author; - + pad.appendRevision(changeset, thisAuthor); - + var correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool); if (correctionChangeset) { pad.appendRevision(correctionChangeset); } - + if (pad.text().lastIndexOf("\n\n") != pad.text().length-2) { var nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length-1, 0, "\n"); pad.appendRevision(nlChangeset); } - + exports.updatePadClients(pad, callback); } ], function(err) @@ -472,29 +483,29 @@ function handleUserChanges(client, message) } exports.updatePadClients = function(pad, callback) -{ +{ //skip this step if noone is on this pad if(!pad2sessions[pad.id]) { callback(); return; } - + //go trough all sessions on this pad async.forEach(pad2sessions[pad.id], function(session, callback) { var lastRev = sessioninfos[session].rev; - + //https://github.com/caolan/async#whilst //send them all new changesets async.whilst( - function (){ return lastRev < pad.getHeadRevisionNumber()}, + function (){ return lastRev < pad.getHeadRevisionNumber(); }, function(callback) { var author, revChangeset; - + var r = ++lastRev; - + async.parallel([ function (callback) { @@ -517,7 +528,7 @@ exports.updatePadClients = function(pad, callback) ], function(err) { if(ERR(err, callback)) return; - + if(author == sessioninfos[session].author) { socketio.sockets.sockets[session].json.send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}}); @@ -528,20 +539,20 @@ exports.updatePadClients = function(pad, callback) var wireMsg = {"type":"COLLABROOM","data":{type:"NEW_CHANGES", newRev:r, changeset: forWire.translated, apool: forWire.pool, - author: author}}; - + author: author}}; + socketio.sockets.sockets[session].json.send(wireMsg); } - + callback(null); }); }, callback ); - + sessioninfos[session].rev = pad.getHeadRevisionNumber(); - },callback); -} + },callback); +}; /** * Copied from the Etherpad Source Code. Don't know what this methode does excatly... @@ -570,7 +581,7 @@ function _correctMarkersInPad(atext, apool) { } } - if (badMarkers.length == 0) { + if (badMarkers.length === 0) { return null; } @@ -586,7 +597,7 @@ function _correctMarkersInPad(atext, apool) { } /** - * Handles a CLIENT_READY. A CLIENT_READY is the first message from the client to the server. The Client sends his token + * Handles a CLIENT_READY. A CLIENT_READY is the first message from the client to the server. The Client sends his token * and the pad it wants to enter. The Server answers with the inital values (clientVars) of the pad * @param client the client that send this message * @param message the message from the client @@ -630,7 +641,7 @@ function handleClientReady(client, message) securityManager.checkAccess (message.padId, message.sessionID, message.token, message.password, function(err, statusObject) { if(ERR(err, callback)) return; - + //access was granted if(statusObject.accessStatus == "grant") { @@ -640,10 +651,10 @@ function handleClientReady(client, message) //no access, send the client a message that tell him why else { - client.json.send({accessStatus: statusObject.accessStatus}) + client.json.send({accessStatus: statusObject.accessStatus}); } }); - }, + }, //get all authordata of this new user function(callback) { @@ -692,7 +703,7 @@ function handleClientReady(client, message) function(callback) { var authors = pad.getAllAuthors(); - + async.parallel([ //get all author data out of the database function(callback) @@ -719,8 +730,8 @@ function handleClientReady(client, message) }); } ], callback); - - + + }, function(callback) { @@ -735,33 +746,33 @@ function handleClientReady(client, message) } } } - + //Save in session2pad that this session belonges to this pad var sessionId=String(client.id); session2pad[sessionId] = message.padId; - + //check if there is already a pad2sessions entry, if not, create one if(!pad2sessions[message.padId]) { pad2sessions[message.padId] = []; } - + //Saves in pad2sessions that this session belongs to this pad pad2sessions[message.padId].push(sessionId); - + //prepare all values for the wire var atext = Changeset.cloneAText(pad.atext); var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool); var apool = attribsForWire.pool.toJsonable(); atext.attribs = attribsForWire.translated; - + //check if abiword is avaiable - var abiwordAvailable = settings.abiword != null ? "yes" : "no"; - if(settings.abiword != null && os.type().indexOf("Windows") != -1) + var abiwordAvailable = settings.abiword ? "yes" : "no"; + if(settings.abiword && os.type().indexOf("Windows") != -1) { abiwordAvailable = "withoutPDF"; } - + var clientVars = { "accountPrivs": { "maxRevisions": 100 @@ -798,20 +809,20 @@ function handleClientReady(client, message) "fullWidth": false, "hideSidebar": false }, - "abiwordAvailable": abiwordAvailable, + "abiwordAvailable": abiwordAvailable, "hooks": {} - } - + }; + //Add a username to the clientVars if one avaiable - if(authorName != null) + if(authorName) { clientVars.userName = authorName; } - + if(sessioninfos[client.id] !== undefined) { //This is a reconnect, so we don't have to send the client the ClientVars again - if(message.reconnect == true) + if(message.reconnect) { //Save the revision in sessioninfos, we take the revision from the info the client send to us sessioninfos[client.id].rev = message.client_rev; @@ -824,11 +835,11 @@ function handleClientReady(client, message) //Save the revision in sessioninfos sessioninfos[client.id].rev = pad.getHeadRevisionNumber(); } - + //Save the revision and the author id in sessioninfos sessioninfos[client.id].author = author; } - + //prepare the notification for the other users on the pad, that this user joined var messageToTheOtherUsers = { "type": "COLLABROOM", @@ -842,18 +853,18 @@ function handleClientReady(client, message) } } }; - + //Add the authorname of this new User, if avaiable - if(authorName != null) + if(authorName) { messageToTheOtherUsers.data.userInfo.name = authorName; } - + //Run trough all sessions of this pad async.forEach(pad2sessions[message.padId], function(sessionID, callback) { var sessionAuthorName, sessionAuthorColorId; - + async.series([ //get the authorname & colorId function(callback) @@ -866,7 +877,7 @@ function handleClientReady(client, message) if(ERR(err, callback)) return; sessionAuthorColorId = value; callback(); - }) + }); }, function(callback) { @@ -875,10 +886,10 @@ function handleClientReady(client, message) if(ERR(err, callback)) return; sessionAuthorName = value; callback(); - }) + }); } ],callback); - }, + }, function (callback) { //Jump over, if this session is the connection session @@ -886,7 +897,7 @@ function handleClientReady(client, message) { //Send this Session the Notification about the new user socketio.sockets.sockets[sessionID].json.send(messageToTheOtherUsers); - + //Send the new User a Notification about this other user var messageToNotifyTheClientAboutTheOthers = { "type": "COLLABROOM", @@ -904,7 +915,7 @@ function handleClientReady(client, message) client.json.send(messageToNotifyTheClientAboutTheOthers); } } - ], callback); + ], callback); }, callback); } ],function(err) diff --git a/node/handler/SocketIORouter.js b/node/handler/SocketIORouter.js index f3b82b8c7..69c6ef145 100644 --- a/node/handler/SocketIORouter.js +++ b/node/handler/SocketIORouter.js @@ -1,5 +1,5 @@ /** - * This is the Socket.IO Router. It routes the Messages between the + * This is the Socket.IO Router. It routes the Messages between the * components of the Server. The components are at the moment: pad and timeslider */ @@ -28,11 +28,11 @@ var securityManager = require("../db/SecurityManager"); * Saves all components * key is the component name * value is the component module - */ + */ var components = {}; var socket; - + /** * adds a component */ @@ -40,10 +40,10 @@ exports.addComponent = function(moduleName, module) { //save the component components[moduleName] = module; - + //give the module the socket module.setSocketIO(socket); -} +}; /** * sets the socket.io and adds event functions for routing @@ -52,31 +52,31 @@ exports.setSocketIO = function(_socket) { //save this socket internaly socket = _socket; - + socket.sockets.on('connection', function(client) { var clientAuthorized = false; - + //wrap the original send function to log the messages client._send = client.send; client.send = function(message) { messageLogger.info("to " + client.id + ": " + stringifyWithoutPassword(message)); client._send(message); - } - + }; + //tell all components about this connect for(var i in components) { components[i].handleConnect(client); } - + //try to handle the message of this client function handleMessage(message) { if(message.component && components[message.component]) { - //check if component is registered in the components array + //check if component is registered in the components array if(components[message.component]) { messageLogger.info("from " + client.id + ": " + stringifyWithoutPassword(message)); @@ -87,8 +87,8 @@ exports.setSocketIO = function(_socket) { messageLogger.error("Can't route the message:" + stringifyWithoutPassword(message)); } - } - + } + client.on('message', function(message) { if(message.protocolVersion && message.protocolVersion != 2) @@ -111,7 +111,7 @@ exports.setSocketIO = function(_socket) securityManager.checkAccess (message.padId, message.sessionID, message.token, message.password, function(err, statusObject) { ERR(err); - + //access was granted, mark the client as authorized and handle the message if(statusObject.accessStatus == "grant") { @@ -143,21 +143,21 @@ exports.setSocketIO = function(_socket) } }); }); -} +}; //returns a stringified representation of a message, removes the password //this ensures there are no passwords in the log function stringifyWithoutPassword(message) { var newMessage = {}; - + for(var i in message) { - if(i == "password" && message[i] != null) - newMessage["password"] = "xxx"; + if(i == "password" && message[i]) + newMessage.password = "xxx"; else newMessage[i]=message[i]; } - + return JSON.stringify(newMessage); } diff --git a/node/handler/TimesliderMessageHandler.js b/node/handler/TimesliderMessageHandler.js index b3493d8cb..647d8754f 100644 --- a/node/handler/TimesliderMessageHandler.js +++ b/node/handler/TimesliderMessageHandler.js @@ -1,6 +1,6 @@ /** - * The MessageHandler handles all Messages that comes from Socket.IO and controls the sessions - */ + * The MessageHandler handles all Messages that comes from Socket.IO and controls the sessions + */ /* * Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka (Primary Technology Ltd) @@ -39,7 +39,7 @@ var socketio; exports.setSocketIO = function(socket_io) { socketio=socket_io; -} +}; /** * Handles the connection of a new user @@ -48,7 +48,7 @@ exports.setSocketIO = function(socket_io) exports.handleConnect = function(client) { -} +}; /** * Handles the disconnection of a user @@ -56,8 +56,8 @@ exports.handleConnect = function(client) */ exports.handleDisconnect = function(client) { - -} + +}; /** * Handles a message from a user @@ -65,7 +65,7 @@ exports.handleDisconnect = function(client) * @param message the message from the client */ exports.handleMessage = function(client, message) -{ +{ //Check what type of message we get and delegate to the other methodes if(message.type == "CLIENT_READY") { @@ -80,70 +80,70 @@ exports.handleMessage = function(client, message) { messageLogger.warn("Dropped message, unknown Message Type: '" + message.type + "'"); } -} +}; function handleClientReady(client, message) { - if(message.padId == null) + if(!message.padId) { messageLogger.warn("Dropped message, changeset request has no padId!"); return; } - + //send the timeslider client the clientVars, with this values its able to start createTimesliderClientVars (message.padId, function(err, clientVars) { ERR(err); - + client.json.send({type: "CLIENT_VARS", data: clientVars}); - }) + }); } /** - * Handles a request for a rough changeset, the timeslider client needs it + * Handles a request for a rough changeset, the timeslider client needs it */ function handleChangesetRequest(client, message) { //check if all ok - if(message.data == null) + if(!message.data) { messageLogger.warn("Dropped message, changeset request has no data!"); return; } - if(message.padId == null) + if(!message.padId) { messageLogger.warn("Dropped message, changeset request has no padId!"); return; } - if(message.data.granularity == null) + if(!message.data.granularity) { messageLogger.warn("Dropped message, changeset request has no granularity!"); return; } - if(message.data.start == null) + if(!message.data.start) { messageLogger.warn("Dropped message, changeset request has no start!"); return; } - if(message.data.requestID == null) + if(!message.data.requestID) { messageLogger.warn("Dropped message, changeset request has no requestID!"); return; } - + var granularity = message.data.granularity; var start = message.data.start; var end = start + (100 * granularity); var padId = message.padId; - + //build the requested rough changesets and send them back getChangesetInfo(padId, start, end, granularity, function(err, changesetInfo) { ERR(err); - + var data = changesetInfo; data.requestID = message.data.requestID; - + client.json.send({type: "CHANGESET_REQ", data: data}); }); } @@ -171,19 +171,19 @@ function createTimesliderClientVars (padId, callback) function(callback) { padManager.getPad(padId, function(err, _pad) - { + { if(ERR(err, callback)) return; pad = _pad; callback(); }); }, - //get all authors and add them to + //get all authors and add them to function(callback) { var historicalAuthorData = {}; //get all authors out of the attribut pool var authors = pad.getAllAuthors(); - + //get all author data out of the database async.forEach(authors, function(authorId, callback) { @@ -216,28 +216,28 @@ function createTimesliderClientVars (padId, callback) { //get the head revision Number var lastRev = pad.getHeadRevisionNumber(); - + //add the revNum to the client Vars clientVars.revNum = lastRev; clientVars.totalRevs = lastRev; - + var atext = Changeset.cloneAText(pad.atext); var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool); var apool = attribsForWire.pool.toJsonable(); atext.attribs = attribsForWire.translated; - + clientVars.initialStyledContents.apool = apool; clientVars.initialStyledContents.atext = atext; - + var granularities = [100, 10, 1]; //get the latest rough changesets async.forEach(granularities, function(granularity, callback) { var topGranularity = granularity*10; - - getChangesetInfo(padId, Math.floor(lastRev / topGranularity)*topGranularity, - Math.floor(lastRev / topGranularity)*topGranularity+topGranularity, granularity, + + getChangesetInfo(padId, Math.floor(lastRev / topGranularity)*topGranularity, + Math.floor(lastRev / topGranularity)*topGranularity+topGranularity, granularity, function(err, changeset) { if(ERR(err, callback)) return; @@ -267,47 +267,47 @@ function getChangesetInfo(padId, startNum, endNum, granularity, callback) var composedChangesets = {}; var revisionDate = []; var lines; - + async.series([ //get the pad from the database function(callback) { padManager.getPad(padId, function(err, _pad) - { + { if(ERR(err, callback)) return; pad = _pad; callback(); }); }, function(callback) - { + { //calculate the last full endnum var lastRev = pad.getHeadRevisionNumber(); if (endNum > lastRev+1) { endNum = lastRev+1; } endNum = Math.floor(endNum / granularity)*granularity; - + var compositesChangesetNeeded = []; var revTimesNeeded = []; - + //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 t1 time we need - revTimesNeeded.push(compositeStart == 0 ? 0 : compositeStart - 1); + revTimesNeeded.push(compositeStart === 0 ? 0 : compositeStart - 1); //add the t2 time we need revTimesNeeded.push(compositeEnd - 1); - + compositeStart += granularity; } - + //get all needed db values parallel async.parallel([ function(callback) @@ -344,58 +344,58 @@ function getChangesetInfo(padId, startNum, endNum, granularity, callback) if(ERR(err, callback)) return; lines = _lines; callback(); - }); + }); } ], callback); }, //doesn't know what happens here excatly :/ function(callback) - { + { var compositeStart = startNum; - - while (compositeStart < endNum) + + while (compositeStart < endNum) { - if (compositeStart + granularity > endNum) + if (compositeStart + granularity > endNum) { break; } - + var compositeEnd = compositeStart + granularity; - + var forwards = composedChangesets[compositeStart + "/" + compositeEnd]; var backwards = Changeset.inverse(forwards, lines.textlines, lines.alines, pad.apool()); - + Changeset.mutateAttributionLines(forwards, lines.alines, pad.apool()); Changeset.mutateTextLines(forwards, lines.textlines); - + var forwards2 = Changeset.moveOpsToNewPool(forwards, pad.apool(), apool); 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]; } - + t2 = revisionDate[compositeEnd - 1]; - + timeDeltas.push(t2 - t1); forwardsChangesets.push(forwards2); backwardsChangesets.push(backwards2); - + compositeStart += granularity; } - + callback(); } ], function(err) { if(ERR(err, callback)) return; - + callback(null, {forwardsChangesets: forwardsChangesets, backwardsChangesets: backwardsChangesets, apool: apool.toJsonable(), @@ -410,7 +410,7 @@ function getChangesetInfo(padId, startNum, endNum, granularity, callback) * Tries to rebuild the getPadLines function of the original Etherpad * https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L263 */ -function getPadLines(padId, revNum, callback) +function getPadLines(padId, revNum, callback) { var atext; var result = {}; @@ -421,7 +421,7 @@ function getPadLines(padId, revNum, callback) function(callback) { padManager.getPad(padId, function(err, _pad) - { + { if(ERR(err, callback)) return; pad = _pad; callback(); @@ -473,7 +473,7 @@ function composePadChangesets(padId, startNum, endNum, callback) function(callback) { padManager.getPad(padId, function(err, _pad) - { + { if(ERR(err, callback)) return; pad = _pad; callback(); @@ -483,14 +483,14 @@ function composePadChangesets(padId, startNum, endNum, callback) function(callback) { var changesetsNeeded=[]; - - //create a array for all changesets, we will + + //create a array for all changesets, we will //replace the values with the changeset later for(var r=startNum;r -1) { //span an abiword process to perform the conversion var abiword = spawn(settings.abiword, ["--to=" + task.destFile, task.srcFile]); - + //delegate the processing of stdout to another function abiword.stdout.on('data', function (data) { @@ -44,7 +44,7 @@ if(os.type().indexOf("Windows") > -1) }); //append error messages to the buffer - abiword.stderr.on('data', function (data) + abiword.stderr.on('data', function (data) { stdoutBuffer += data.toString(); }); @@ -52,19 +52,19 @@ if(os.type().indexOf("Windows") > -1) //throw exceptions if abiword is dieing abiword.on('exit', function (code) { - if(code != 0) { + if(code !== 0) { throw "Abiword died with exit code " + code; } - if(stdoutBuffer != "") + if(stdoutBuffer !== "") { console.log(stdoutBuffer); } callback(); }); - } - + }; + exports.convertFile = function(srcFile, destFile, type, callback) { doConvertTask({"srcFile": srcFile, "destFile": destFile, "type": type}, callback); @@ -78,67 +78,68 @@ else var abiword = spawn(settings.abiword, ["--plugin", "AbiCommand"]); //append error messages to the buffer - abiword.stderr.on('data', function (data) + abiword.stderr.on('data', function (data) { stdoutBuffer += data.toString(); }); //throw exceptions if abiword is dieing - abiword.on('exit', function (code) + abiword.on('exit', function (code) { throw "Abiword died with exit code " + code; }); - //delegate the processing of stdout to a other function - abiword.stdout.on('data',onAbiwordStdout); var stdoutCallback = null; var stdoutBuffer = ""; var firstPrompt = true; - function onAbiwordStdout(data) + var onAbiwordStdout = function onAbiwordStdout(data) { //add data to buffer stdoutBuffer+=data.toString(); - + //we're searching for the prompt, cause this means everything we need is in the buffer if(stdoutBuffer.search("AbiWord:>") != -1) { //filter the feedback message var err = stdoutBuffer.search("OK") != -1 ? null : stdoutBuffer; - + //reset the buffer stdoutBuffer = ""; - + //call the callback with the error message //skip the first prompt - if(stdoutCallback != null && !firstPrompt) + if(stdoutCallback && !firstPrompt) { stdoutCallback(err); stdoutCallback = null; } - + firstPrompt = false; } - } + }; + + //delegate the processing of stdout to a other function + abiword.stdout.on('data',onAbiwordStdout); doConvertTask = function(task, callback) { abiword.stdin.write("convert " + task.srcFile + " " + task.destFile + " " + task.type + "\n"); - + //create a callback that calls the task callback and the caller callback stdoutCallback = function (err) { callback(); task.callback(err); }; - } - + }; + //Queue with the converts we have to do var queue = async.queue(doConvertTask, 1); - + exports.convertFile = function(srcFile, destFile, type, callback) - { + { queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback}); }; } diff --git a/node/utils/AttributePoolFactory.js b/node/utils/AttributePoolFactory.js index 807c2b393..261e0c501 100644 --- a/node/utils/AttributePoolFactory.js +++ b/node/utils/AttributePoolFactory.js @@ -1,8 +1,8 @@ /** - * This code represents the Attribute Pool Object of the original Etherpad. + * This code represents the Attribute Pool Object of the original Etherpad. * 90% of the code is still like in the original Etherpad * Look at https://github.com/ether/pad/blob/master/infrastructure/ace/www/easysync2.js - * You can find a explanation what a attribute pool is here: + * You can find a explanation what a attribute pool is here: * https://github.com/Pita/etherpad-lite/blob/master/doc/easysync/easysync-notes.txt */ @@ -87,4 +87,4 @@ exports.createAttributePool = function () { }; return p; -} +}; diff --git a/node/utils/Changeset.js b/node/utils/Changeset.js index 9e1b60ebe..09276cef9 100644 --- a/node/utils/Changeset.js +++ b/node/utils/Changeset.js @@ -1,10 +1,10 @@ /* * This is the Changeset library copied from the old Etherpad with some modifications to use it in node.js * Can be found in https://github.com/ether/pad/blob/master/infrastructure/ace/www/easysync2.js - */ + */ /** - * This code is mostly from the old Etherpad. Please help us to comment this code. + * This code is mostly from the old Etherpad. Please help us to comment this code. * This helps other people to understand this code better and helps them to improve it. * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED */ @@ -66,7 +66,7 @@ exports.newLen = function (cs) { exports.opIterator = function (opsStr, optStartIndex) { //print(opsStr); - var regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|\?|/g; + var regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([\-+=])([0-9a-z]+)|\?|/g; var startIndex = (optStartIndex || 0); var curIndex = startIndex; var prevIndex = curIndex; @@ -195,12 +195,10 @@ exports.checkRep = function (cs) { exports.assert(oldPos < oldLen, oldPos, " >= ", oldLen, " in ", cs); break; case '+': - { - calcNewLen += o.chars; - numInserted += o.chars; - exports.assert(calcNewLen < newLen, calcNewLen, " >= ", newLen, " in ", cs); - break; - } + calcNewLen += o.chars; + numInserted += o.chars; + exports.assert(calcNewLen < newLen, calcNewLen, " >= ", newLen, " in ", cs); + break; } assem.append(o); } @@ -216,7 +214,7 @@ exports.checkRep = function (cs) { exports.assert(normalized == cs, normalized, ' != ', cs); return cs; -} +}; exports.smartOpAssembler = function () { // Like opAssembler but able to produce conforming exportss @@ -387,7 +385,7 @@ if (_opt) { bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars; bufOp.lines += op.lines; bufOpAdditionalCharsAfterNewline = 0; - } else if (bufOp.lines == 0) { + } else if (bufOp.lines === 0) { // both bufOp and op are in-line bufOp.chars += op.chars; } else { @@ -635,9 +633,9 @@ exports.textLinesMutator = function (lines) { } //print(inSplice+" / "+isCurLineInSplice()+" / "+curSplice[0]+" / "+curSplice[1]+" / "+lines.length); /*if (inSplice && (! isCurLineInSplice()) && (curSplice[0] + curSplice[1] < lines.length)) { - print("BLAH"); - putCurLineInSplice(); - }*/ + print("BLAH"); + putCurLineInSplice(); + }*/ // tests case foo in remove(), which isn't otherwise covered in current impl } //debugPrint("skip"); @@ -667,13 +665,13 @@ exports.textLinesMutator = function (lines) { enterSplice(); } - function nextKLinesText(k) { + var nextKLinesText = function nextKLinesText(k) { var m = curSplice[0] + curSplice[1]; return lines_slice(m, m + k).join(''); - } + }; if (isCurLineInSplice()) { //print(curCol); - if (curCol == 0) { + if (curCol === 0) { removed = curSplice[curSplice.length - 1]; // print("FOO"); // case foo curSplice.length--; @@ -719,6 +717,7 @@ exports.textLinesMutator = function (lines) { if (!inSplice) { enterSplice(); } + var sline; if (L) { var newLines = exports.splitTextLines(text); if (isCurLineInSplice()) { @@ -729,7 +728,7 @@ exports.textLinesMutator = function (lines) { //curLine += newLines.length; //} //else { - var sline = curSplice.length - 1; + sline = curSplice.length - 1; var theLine = curSplice[sline]; var lineCol = curCol; curSplice[sline] = theLine.substring(0, lineCol) + newLines[0]; @@ -745,7 +744,7 @@ exports.textLinesMutator = function (lines) { curLine += newLines.length; } } else { - var sline = putCurLineInSplice(); + sline = putCurLineInSplice(); curSplice[sline] = curSplice[sline].substring(0, curCol) + text + curSplice[sline].substring(curCol); curCol += text.length; } @@ -949,74 +948,66 @@ exports._slicerZipperFunc = function (attOp, csOp, opOut, pool) { } else { switch (csOp.opcode) { case '-': - { - if (csOp.chars <= attOp.chars) { - // delete or delete part - if (attOp.opcode == '=') { - opOut.opcode = '-'; - opOut.chars = csOp.chars; - opOut.lines = csOp.lines; - opOut.attribs = ''; - } - attOp.chars -= csOp.chars; - attOp.lines -= csOp.lines; - csOp.opcode = ''; - if (!attOp.chars) { - attOp.opcode = ''; - } - } else { - // delete and keep going - if (attOp.opcode == '=') { - opOut.opcode = '-'; - opOut.chars = attOp.chars; - opOut.lines = attOp.lines; - opOut.attribs = ''; - } - csOp.chars -= attOp.chars; - csOp.lines -= attOp.lines; - attOp.opcode = ''; - } - break; - } - case '+': - { - // insert - exports.copyOp(csOp, opOut); - csOp.opcode = ''; - break; - } - case '=': - { - if (csOp.chars <= attOp.chars) { - // keep or keep part - opOut.opcode = attOp.opcode; + if (csOp.chars <= attOp.chars) { + // delete or delete part + if (attOp.opcode == '=') { + opOut.opcode = '-'; opOut.chars = csOp.chars; opOut.lines = csOp.lines; - opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool); - csOp.opcode = ''; - attOp.chars -= csOp.chars; - attOp.lines -= csOp.lines; - if (!attOp.chars) { - attOp.opcode = ''; - } - } else { - // keep and keep going - opOut.opcode = attOp.opcode; + opOut.attribs = ''; + } + attOp.chars -= csOp.chars; + attOp.lines -= csOp.lines; + csOp.opcode = ''; + if (!attOp.chars) { + attOp.opcode = ''; + } + } else { + // delete and keep going + if (attOp.opcode == '=') { + opOut.opcode = '-'; opOut.chars = attOp.chars; opOut.lines = attOp.lines; - opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool); - attOp.opcode = ''; - csOp.chars -= attOp.chars; - csOp.lines -= attOp.lines; + opOut.attribs = ''; } - break; - } - case '': - { - exports.copyOp(attOp, opOut); + csOp.chars -= attOp.chars; + csOp.lines -= attOp.lines; attOp.opcode = ''; - break; } + break; + case '+': + // insert + exports.copyOp(csOp, opOut); + csOp.opcode = ''; + break; + case '=': + if (csOp.chars <= attOp.chars) { + // keep or keep part + opOut.opcode = attOp.opcode; + opOut.chars = csOp.chars; + opOut.lines = csOp.lines; + opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool); + csOp.opcode = ''; + attOp.chars -= csOp.chars; + attOp.lines -= csOp.lines; + if (!attOp.chars) { + attOp.opcode = ''; + } + } else { + // keep and keep going + opOut.opcode = attOp.opcode; + opOut.chars = attOp.chars; + opOut.lines = attOp.lines; + opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool); + attOp.opcode = ''; + csOp.chars -= attOp.chars; + csOp.lines -= attOp.lines; + } + break; + case '': + exports.copyOp(attOp, opOut); + attOp.opcode = ''; + break; } } }; @@ -1470,7 +1461,7 @@ exports.appendATextToAssembler = function (atext, assem) { }; exports.prepareForWire = function (cs, pool) { - var newPool = AttributePoolFactory.createAttributePool();; + var newPool = AttributePoolFactory.createAttributePool(); var newCs = exports.moveOpsToNewPool(cs, pool, newPool); return { translated: newCs, @@ -1480,7 +1471,7 @@ exports.prepareForWire = function (cs, pool) { exports.isIdentity = function (cs) { var unpacked = exports.unpack(cs); - return unpacked.ops == "" && unpacked.oldLen == unpacked.newLen; + return unpacked.ops === "" && unpacked.oldLen == unpacked.newLen; }; exports.opAttributeValue = function (op, key, pool) { diff --git a/node/utils/ExportDokuWiki.js b/node/utils/ExportDokuWiki.js index 48e4b2915..395b4b3c8 100644 --- a/node/utils/ExportDokuWiki.js +++ b/node/utils/ExportDokuWiki.js @@ -1,12 +1,12 @@ /** * Copyright 2011 Adrian Lang - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS-IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -28,7 +28,7 @@ function getPadDokuWiki(pad, revNum, callback) function (callback) { - if (revNum != undefined) + if (revNum) { pad.getInternalRevisionAText(revNum, function (err, revisionAtext) { @@ -122,6 +122,8 @@ function getDokuWikiFromAtext(pad, atext) var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars)); idx += numChars; + var i; + while (iter.hasNext()) { var o = iter.next(); @@ -142,7 +144,7 @@ function getDokuWikiFromAtext(pad, atext) } } }); - for (var i = 0; i < propVals.length; i++) + for (i = 0; i < propVals.length; i++) { if (propVals[i] === true) { @@ -160,7 +162,7 @@ function getDokuWikiFromAtext(pad, atext) { // leaving bold (e.g.) also leaves italics, etc. var left = false; - for (var i = 0; i < propVals.length; i++) + for (i = 0; i < propVals.length; i++) { var v = propVals[i]; if (!left) @@ -179,7 +181,7 @@ function getDokuWikiFromAtext(pad, atext) } } - for (var i = propVals.length - 1; i >= 0; i--) + for (i = propVals.length - 1; i >= 0; i--) { if (propVals[i] === LEAVE) { @@ -191,7 +193,7 @@ function getDokuWikiFromAtext(pad, atext) emitCloseTag(i); } } - for (var i = 0; i < propVals.length; i++) + for (i = 0; i < propVals.length; i++) { if (propVals[i] === ENTER || propVals[i] === STAY) { @@ -210,7 +212,7 @@ function getDokuWikiFromAtext(pad, atext) assem.append(_escapeDokuWiki(s)); } // end iteration over spans in line - for (var i = propVals.length - 1; i >= 0; i--) + for (i = propVals.length - 1; i >= 0; i--) { if (propVals[i]) { @@ -310,7 +312,7 @@ exports.getPadDokuWikiDocument = function (padId, revNum, callback) getPadDokuWiki(pad, revNum, callback); }); -} +}; function _escapeDokuWiki(s) { @@ -321,7 +323,7 @@ function _escapeDokuWiki(s) // copied from ACE var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; var _REGEX_SPACE = /\s/; -var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')'); +var _REGEX_URLCHAR = new RegExp('(' + (/[\-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/).source + '|' + _REGEX_WORDCHAR.source + ')'); var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g'); // returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...] diff --git a/node/utils/ExportHtml.js b/node/utils/ExportHtml.js index 46ed980a5..1e5374561 100644 --- a/node/utils/ExportHtml.js +++ b/node/utils/ExportHtml.js @@ -1,12 +1,12 @@ /** * Copyright 2009 Google Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS-IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -55,7 +55,7 @@ function getPadHTML(pad, revNum, callback) function (callback) { - if (revNum != undefined) + if (revNum) { pad.getInternalRevisionAText(revNum, function (err, revisionAtext) { @@ -140,7 +140,7 @@ function getHTMLFromAtext(pad, atext) assem.append(tags[i]); assem.append('>'); } - + function orderdCloseTags(tags2close) { for(var i=0;i= 0; i--) + for (i = propVals.length - 1; i >= 0; i--) { if (propVals[i] === LEAVE) { @@ -244,10 +248,10 @@ function getHTMLFromAtext(pad, atext) tags2close.push(i); } } - + orderdCloseTags(tags2close); - - for (var i = 0; i < propVals.length; i++) + + for (i = 0; i < propVals.length; i++) { if (propVals[i] === ENTER || propVals[i] === STAY) { @@ -262,26 +266,26 @@ function getHTMLFromAtext(pad, atext) { chars--; // exclude newline at end of line, if present } - + var s = taker.take(chars); - - //removes the characters with the code 12. Don't know where they come + + //removes the characters with the code 12. Don't know where they come //from but they break the abiword parser and are completly useless s = s.replace(String.fromCharCode(12), ""); - + assem.append(_escapeHTML(s)); } // end iteration over spans in line - - var tags2close = []; - for (var i = propVals.length - 1; i >= 0; i--) + + tags2close = []; + for (var x = propVals.length - 1; x >= 0; x--) { - if (propVals[i]) + if (propVals[x]) { - tags2close.push(i); - propVals[i] = false; + tags2close.push(x); + propVals[x] = false; } } - + orderdCloseTags(tags2close); } // end processNextChars if (urls) @@ -425,7 +429,7 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback) callback(null, head + html + foot); }); }); -} +}; function _escapeHTML(s) { @@ -436,18 +440,18 @@ function _escapeHTML(s) re.MAP = { '&': '&', '<': '<', - '>': '>', + '>': '>' }; } - + s = s.replace(re, function (c) { return re.MAP[c]; }); - + return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c) { - return "&#" +c.charCodeAt(0) + ";" + return "&#" +c.charCodeAt(0) + ";"; }); } @@ -467,15 +471,19 @@ function _processSpaces(s) { parts.push(m); }); + + var i; + var p; + if (doesWrap) { var endOfLine = true; var beforeSpace = false; // last space in a run is normal, others are nbsp, // end of line is nbsp - for (var i = parts.length - 1; i >= 0; i--) + for (i = parts.length - 1; i >= 0; i--) { - var p = parts[i]; + p = parts[i]; if (p == " ") { if (endOfLine || beforeSpace) parts[i] = ' '; @@ -489,9 +497,9 @@ function _processSpaces(s) } } // beginning of line is nbsp - for (var i = 0; i < parts.length; i++) + for (i = 0; i < parts.length; i++) { - var p = parts[i]; + p = parts[i]; if (p == " ") { parts[i] = ' '; @@ -505,9 +513,9 @@ function _processSpaces(s) } else { - for (var i = 0; i < parts.length; i++) + for (i = 0; i < parts.length; i++) { - var p = parts[i]; + p = parts[i]; if (p == " ") { parts[i] = ' '; @@ -521,7 +529,7 @@ function _processSpaces(s) // copied from ACE var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; var _REGEX_SPACE = /\s/; -var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')'); +var _REGEX_URLCHAR = new RegExp('(' + (/[\-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/).source + '|' + _REGEX_WORDCHAR.source + ')'); var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g'); // returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...] diff --git a/node/utils/Minify.js b/node/utils/Minify.js index fd1dd0793..18d131639 100644 --- a/node/utils/Minify.js +++ b/node/utils/Minify.js @@ -1,7 +1,7 @@ /** - * This Module manages all /minified/* requests. It controls the - * minification && compression of Javascript and CSS. - */ + * This Module manages all /minified/\* requests. It controls the + * minification && compression of Javascript and CSS. + */ /* * 2011 Peter 'Pita' Martischka (Primary Technology Ltd) @@ -28,7 +28,7 @@ var jsp = require("uglify-js").parser; var pro = require("uglify-js").uglify; var path = require('path'); var Buffer = require('buffer').Buffer; -var gzip = require('gzip'); +var zlib = require('zlib'); var server = require('../server'); var os = require('os'); @@ -44,7 +44,7 @@ var timesliderJS = ["jquery.min.js", "plugins.js", "undo-xpopup.js", "json2.js", exports.minifyJS = function(req, res, jsFilename) { res.header("Content-Type","text/javascript"); - + //choose the js files we need if(jsFilename == "pad.js") { @@ -58,20 +58,22 @@ exports.minifyJS = function(req, res, jsFilename) { throw new Error("there is no profile for creating " + name); } - + + var fileValues; + //minifying is enabled if(settings.minify) { - var fileValues = {}; + fileValues = {}; var embeds = {}; var latestModification = 0; - + async.series([ //find out the highest modification date function(callback) - { + { var folders2check = ["../static/css","../static/js"]; - + //go trough this two folders async.forEach(folders2check, function(path, callback) { @@ -79,27 +81,27 @@ exports.minifyJS = function(req, res, jsFilename) fs.readdir(path, function(err, files) { if(ERR(err, callback)) return; - + //we wanna check the directory itself for changes too files.push("."); - + //go trough all files in this folder - async.forEach(files, function(filename, callback) + async.forEach(files, function(filename, callback) { //get the stat data of this file fs.stat(path + "/" + filename, function(err, stats) { if(ERR(err, callback)) return; - + //get the modification time var modificationTime = stats.mtime.getTime(); - + //compare the modification time to the highest found if(modificationTime > latestModification) { latestModification = modificationTime; } - + callback(); }); }, callback); @@ -116,7 +118,7 @@ exports.minifyJS = function(req, res, jsFilename) ERR(err, callback); return; } - + //there is no minfied file or there new changes since this file was generated, so continue generating this file if((err && err.code == "ENOENT") || stats.mtime.getTime() < latestModification) { @@ -128,48 +130,48 @@ exports.minifyJS = function(req, res, jsFilename) callback("stop"); } }); - }, + }, //load all js files function (callback) { async.forEach(jsFiles, function (item, callback) { fs.readFile("../static/js/" + item, "utf-8", function(err, data) - { + { if(ERR(err, callback)) return; fileValues[item] = data; callback(); }); }, callback); }, - //find all includes in ace.js and embed them + //find all includes in ace.js and embed them function(callback) - { + { //if this is not the creation of pad.js, skip this part if(jsFilename != "pad.js") { callback(); return; } - + var founds = fileValues["ace.js"].match(/\$\$INCLUDE_[a-zA-Z_]+\([a-zA-Z0-9.\/_"]+\)/gi); - + //go trough all includes async.forEach(founds, function (item, callback) { var filename = item.match(/"[^"]*"/g)[0].substr(1); filename = filename.substr(0,filename.length-1); - + var type = item.match(/INCLUDE_[A-Z]+/g)[0].substr("INCLUDE_".length); - + var quote = item.search("_Q") != -1; - + //read the included file fs.readFile(filename, "utf-8", function(err, data) - { + { if(ERR(err, callback)) return; - - //compress the file + + //compress the file if(type == "JS") { embeds[item] = "