This commit is contained in:
GitHub Merge Button 2012-01-08 06:49:59 -08:00
commit f60a9cce17
27 changed files with 1353 additions and 1344 deletions

View file

@ -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. 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 # Modules created for this project
* [ueberDB](https://github.com/Pita/ueberDB) "transforms every database into a object key value store" - manages all database access * [ueberDB](https://github.com/Pita/ueberDB) "transforms every database into a object key value store" - manages all database access

9
bin/jshint.sh Executable file
View file

@ -0,0 +1,9 @@
#!/bin/sh
if [ -d "../bin" ]; then
cd "../"
fi
JSHINT=./node_modules/jshint/bin/hint
$JSHINT ./node/

View file

@ -29,7 +29,7 @@ var sessionManager = require("./SessionManager");
var async = require("async"); var async = require("async");
var exportHtml = require("../utils/ExportHtml"); var exportHtml = require("../utils/ExportHtml");
var importHtml = require("../utils/ImportHtml"); var importHtml = require("../utils/ImportHtml");
var cleanText = require("./Pad").cleanText; var cleanText = require("../utils/cleantext").cleanText;
/**********************/ /**********************/
/**GROUP FUNCTIONS*****/ /**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: Example returns:
@ -78,14 +78,14 @@ exports.getText = function(padID, rev, callback)
callback = rev; callback = rev;
rev = undefined; rev = undefined;
} }
//check if rev is a number //check if rev is a number
if(rev !== undefined && typeof rev != "number") if(rev !== undefined && typeof rev != "number")
{ {
//try to parse the number //try to parse the number
if(!isNaN(parseInt(rev))) if(!isNaN(parseInt(rev, 10)))
{ {
rev = parseInt(rev); rev = parseInt(rev, 10);
} }
else else
{ {
@ -93,26 +93,26 @@ exports.getText = function(padID, rev, callback)
return; return;
} }
} }
//ensure this is not a negativ number //ensure this is not a negativ number
if(rev !== undefined && rev < 0) if(rev !== undefined && rev < 0)
{ {
callback(new customError("rev is a negativ number","apierror")); callback(new customError("rev is a negativ number","apierror"));
return; return;
} }
//ensure this is not a float value //ensure this is not a float value
if(rev !== undefined && !is_int(rev)) if(rev !== undefined && !is_int(rev))
{ {
callback(new customError("rev is a float value","apierror")); callback(new customError("rev is a float value","apierror"));
return; return;
} }
//get the pad //get the pad
getPadSafe(padID, true, function(err, pad) getPadSafe(padID, true, function(err, pad)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//the client asked for a special revision //the client asked for a special revision
if(rev !== undefined) 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")); callback(new customError("rev is higher than the head revision of the pad","apierror"));
return; return;
} }
//get the text of this revision //get the text of this revision
pad.getInternalRevisionAText(rev, function(err, atext) pad.getInternalRevisionAText(rev, function(err, atext)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
data = {text: atext.text}; data = {text: atext.text};
callback(null, data); callback(null, data);
}) });
} }
//the client wants the latest text, lets return it to him //the client wants the latest text, lets return it to him
else else
@ -139,10 +139,10 @@ exports.getText = function(padID, rev, callback)
callback(null, {"text": pad.text()}); 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: Example returns:
@ -151,22 +151,22 @@ Example returns:
{code: 1, message:"text too long", data: null} {code: 1, message:"text too long", data: null}
*/ */
exports.setText = function(padID, text, callback) exports.setText = function(padID, text, callback)
{ {
//get the pad //get the pad
getPadSafe(padID, true, function(err, pad) getPadSafe(padID, true, function(err, pad)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//set the text //set the text
pad.setText(text); pad.setText(text);
//update the clients on the pad //update the clients on the pad
padMessageHandler.updatePadClients(pad, callback); padMessageHandler.updatePadClients(pad, callback);
}); });
} };
/** /**
getHTML(padID, [rev]) returns the html of a pad getHTML(padID, [rev]) returns the html of a pad
Example returns: Example returns:
@ -178,14 +178,14 @@ exports.getHTML = function(padID, rev, callback)
if(typeof rev == "function") if(typeof rev == "function")
{ {
callback = rev; callback = rev;
rev = undefined; rev = undefined;
} }
if (rev !== undefined && typeof rev != "number") if (rev !== undefined && typeof rev != "number")
{ {
if (!isNaN(parseInt(rev))) if (!isNaN(parseInt(rev, 10)))
{ {
rev = parseInt(rev); rev = parseInt(rev, 10);
} }
else else
{ {
@ -209,7 +209,7 @@ exports.getHTML = function(padID, rev, callback)
getPadSafe(padID, true, function(err, pad) getPadSafe(padID, true, function(err, pad)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//the client asked for a special revision //the client asked for a special revision
if(rev !== undefined) 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")); callback(new customError("rev is higher than the head revision of the pad","apierror"));
return; return;
} }
//get the html of this revision //get the html of this revision
exportHtml.getPadHTML(pad, rev, function(err, html) exportHtml.getPadHTML(pad, rev, function(err, html)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
@ -234,14 +234,14 @@ exports.getHTML = function(padID, rev, callback)
exportHtml.getPadHTML(pad, undefined, function (err, html) exportHtml.getPadHTML(pad, undefined, function (err, html)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
data = {html: html}; data = {html: html};
callback(null, data); callback(null, data);
}); });
} }
}); });
} };
exports.setHTML = function(padID, html, callback) exports.setHTML = function(padID, html, callback)
{ {
@ -257,14 +257,14 @@ exports.setHTML = function(padID, html, callback)
padMessageHandler.updatePadClients(pad, callback); padMessageHandler.updatePadClients(pad, callback);
}); });
} };
/*****************/ /*****************/
/**PAD FUNCTIONS */ /**PAD FUNCTIONS */
/*****************/ /*****************/
/** /**
getRevisionsCount(padID) returns the number of revisions of this pad getRevisionsCount(padID) returns the number of revisions of this pad
Example returns: Example returns:
@ -277,13 +277,13 @@ exports.getRevisionsCount = function(padID, callback)
getPadSafe(padID, true, function(err, pad) getPadSafe(padID, true, function(err, pad)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
callback(null, {revisions: pad.getHeadRevisionNumber()}); 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: Example returns:
@ -291,24 +291,24 @@ Example returns:
{code: 1, message:"pad does already exist", data: null} {code: 1, message:"pad does already exist", data: null}
*/ */
exports.createPad = function(padID, text, callback) exports.createPad = function(padID, text, callback)
{ {
//ensure there is no $ in the padID //ensure there is no $ in the padID
if(padID && padID.indexOf("$") != -1) if(padID && padID.indexOf("$") != -1)
{ {
callback(new customError("createPad can't create group pads","apierror")); callback(new customError("createPad can't create group pads","apierror"));
return; return;
} }
//create pad //create pad
getPadSafe(padID, false, text, function(err) getPadSafe(padID, false, text, function(err)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
callback(); callback();
}); });
} };
/** /**
deletePad(padID) deletes a pad deletePad(padID) deletes a pad
Example returns: Example returns:
@ -320,13 +320,13 @@ exports.deletePad = function(padID, callback)
getPadSafe(padID, true, function(err, pad) getPadSafe(padID, true, function(err, pad)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
pad.remove(callback); 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: Example returns:
@ -339,7 +339,7 @@ exports.getReadOnlyID = function(padID, callback)
getPadSafe(padID, true, function(err) getPadSafe(padID, true, function(err)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//get the readonlyId //get the readonlyId
readOnlyManager.getReadOnlyId(padID, function(err, readOnlyId) readOnlyManager.getReadOnlyId(padID, function(err, readOnlyId)
{ {
@ -347,10 +347,10 @@ exports.getReadOnlyID = function(padID, callback)
callback(null, {readOnlyID: readOnlyId}); 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: Example returns:
@ -370,20 +370,20 @@ exports.setPublicStatus = function(padID, publicStatus, callback)
getPadSafe(padID, true, function(err, pad) getPadSafe(padID, true, function(err, pad)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//convert string to boolean //convert string to boolean
if(typeof publicStatus == "string") if(typeof publicStatus == "string")
publicStatus = publicStatus == "true" ? true : false; publicStatus = publicStatus == "true" ? true : false;
//set the password //set the password
pad.setPublicStatus(publicStatus); pad.setPublicStatus(publicStatus);
callback(); callback();
}); });
} };
/** /**
getPublicStatus(padID) return true of false getPublicStatus(padID) return true of false
Example returns: 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")); callback(new customError("You can only get/set the publicStatus of pads that belong to a group","apierror"));
return; return;
} }
//get the pad //get the pad
getPadSafe(padID, true, function(err, pad) getPadSafe(padID, true, function(err, pad)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
callback(null, {publicStatus: pad.getPublicStatus()}); 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: 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")); callback(new customError("You can only get/set the password of pads that belong to a group","apierror"));
return; return;
} }
//get the pad //get the pad
getPadSafe(padID, true, function(err, pad) getPadSafe(padID, true, function(err, pad)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//set the password //set the password
pad.setPassword(password); pad.setPassword(password);
callback(); callback();
}); });
} };
/** /**
isPasswordProtected(padID) returns true or false isPasswordProtected(padID) returns true or false
Example returns: Example returns:
@ -458,10 +458,10 @@ exports.isPasswordProtected = function(padID, callback)
getPadSafe(padID, true, function(err, pad) getPadSafe(padID, true, function(err, pad)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
callback(null, {isPasswordProtected: pad.isPasswordProtected()}); callback(null, {isPasswordProtected: pad.isPasswordProtected()});
}); });
} };
/******************************/ /******************************/
/** INTERNAL HELPER FUNCTIONS */ /** INTERNAL HELPER FUNCTIONS */
@ -469,8 +469,8 @@ exports.isPasswordProtected = function(padID, callback)
//checks if a number is an int //checks if a number is an int
function is_int(value) function is_int(value)
{ {
return (parseFloat(value) == parseInt(value)) && !isNaN(value) return (parseFloat(value) == parseInt(value, 10)) && !isNaN(value);
} }
//gets a pad safe //gets a pad safe
@ -488,26 +488,26 @@ function getPadSafe(padID, shouldExist, text, callback)
callback(new customError("padID is not a string","apierror")); callback(new customError("padID is not a string","apierror"));
return; return;
} }
//check if the padID maches the requirements //check if the padID maches the requirements
if(!padManager.isValidPadId(padID)) if(!padManager.isValidPadId(padID))
{ {
callback(new customError("padID did not match requirements","apierror")); callback(new customError("padID did not match requirements","apierror"));
return; return;
} }
//check if the pad exists //check if the pad exists
padManager.doesPadExists(padID, function(err, exists) padManager.doesPadExists(padID, function(err, exists)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//does not exist, but should //does not exist, but should
if(exists == false && shouldExist == true) if(!exists && shouldExist)
{ {
callback(new customError("padID does not exist","apierror")); callback(new customError("padID does not exist","apierror"));
} }
//does exists, but shouldn't //does exists, but shouldn't
else if(exists == true && shouldExist == false) else if(exists && !shouldExist)
{ {
callback(new customError("padID does already exist","apierror")); callback(new customError("padID does already exist","apierror"));
} }

View file

@ -31,14 +31,14 @@ exports.doesAuthorExists = function (authorID, callback)
db.get("globalAuthor:" + authorID, function (err, author) db.get("globalAuthor:" + authorID, function (err, author)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
callback(null, author != null); callback(null, author ? true : false);
}); });
} };
/** /**
* Returns the AuthorID for a token. * Returns the AuthorID for a token.
* @param {String} token The token * @param {String} token The token
* @param {Function} callback callback (err, author) * @param {Function} callback callback (err, author)
*/ */
exports.getAuthor4Token = function (token, callback) exports.getAuthor4Token = function (token, callback)
{ {
@ -48,52 +48,54 @@ exports.getAuthor4Token = function (token, callback)
//return only the sub value authorID //return only the sub value authorID
callback(null, author ? author.authorID : author); callback(null, author ? author.authorID : author);
}); });
} };
/** /**
* Returns the AuthorID for a mapper. * Returns the AuthorID for a mapper.
* @param {String} token The mapper * @param {String} token The mapper
* @param {Function} callback callback (err, author) * @param {Function} callback callback (err, author)
*/ */
exports.createAuthorIfNotExistsFor = function (authorMapper, name, callback) exports.createAuthorIfNotExistsFor = function (authorMapper, name, callback)
{ {
mapAuthorWithDBKey("mapper2author", authorMapper, function(err, author) mapAuthorWithDBKey("mapper2author", authorMapper, function(err, author)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//set the name of this author //set the name of this author
if(name) if(name)
{
exports.setAuthorName(author.authorID, name); exports.setAuthorName(author.authorID, name);
}
//return the authorID //return the authorID
callback(null, author); callback(null, author);
}); });
} };
/** /**
* Returns the AuthorID for a mapper. We can map using a mapperkey, * Returns the AuthorID for a mapper. We can map using a mapperkey,
* so far this is token2author and mapper2author * 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 {String} mapper The mapper
* @param {Function} callback callback (err, author) * @param {Function} callback callback (err, author)
*/ */
function mapAuthorWithDBKey (mapperkey, mapper, callback) function mapAuthorWithDBKey (mapperkey, mapper, callback)
{ {
//try to map to an author //try to map to an author
db.get(mapperkey + ":" + mapper, function (err, author) db.get(mapperkey + ":" + mapper, function (err, author)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//there is no author with this mapper, so create one //there is no author with this mapper, so create one
if(author == null) if(!author)
{ {
exports.createAuthor(null, function(err, author) exports.createAuthor(null, function(err, author)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//create the token2author relation //create the token2author relation
db.set(mapperkey + ":" + mapper, author.authorID); db.set(mapperkey + ":" + mapper, author.authorID);
//return the author //return the author
callback(null, author); callback(null, author);
}); });
@ -103,7 +105,7 @@ function mapAuthorWithDBKey (mapperkey, mapper, callback)
{ {
//update the timestamp of this author //update the timestamp of this author
db.setSub("globalAuthor:" + author, ["timestamp"], new Date().getTime()); db.setSub("globalAuthor:" + author, ["timestamp"], new Date().getTime());
//return the author //return the author
callback(null, {authorID: author}); callback(null, {authorID: author});
} }
@ -111,22 +113,22 @@ function mapAuthorWithDBKey (mapperkey, mapper, callback)
} }
/** /**
* Internal function that creates the database entry for an author * Internal function that creates the database entry for an author
* @param {String} name The name of the author * @param {String} name The name of the author
*/ */
exports.createAuthor = function(name, callback) exports.createAuthor = function(name, callback)
{ {
//create the new author name //create the new author name
var author = "a." + randomString(16); var author = "a." + randomString(16);
//create the globalAuthors db entry //create the globalAuthors db entry
var authorObj = {"colorId" : Math.floor(Math.random()*32), "name": name, "timestamp": new Date().getTime()}; var authorObj = {"colorId" : Math.floor(Math.random()*32), "name": name, "timestamp": new Date().getTime()};
//set the global author db entry //set the global author db entry
db.set("globalAuthor:" + author, authorObj); db.set("globalAuthor:" + author, authorObj);
callback(null, {authorID: author}); callback(null, {authorID: author});
} };
/** /**
* Returns the Author Obj of the author * Returns the Author Obj of the author
@ -136,7 +138,7 @@ exports.createAuthor = function(name, callback)
exports.getAuthor = function (author, callback) exports.getAuthor = function (author, callback)
{ {
db.get("globalAuthor:" + author, callback); db.get("globalAuthor:" + author, callback);
} };
/** /**
* Returns the color Id of the author * Returns the color Id of the author
@ -146,7 +148,7 @@ exports.getAuthor = function (author, callback)
exports.getAuthorColorId = function (author, callback) exports.getAuthorColorId = function (author, callback)
{ {
db.getSub("globalAuthor:" + author, ["colorId"], callback); db.getSub("globalAuthor:" + author, ["colorId"], callback);
} };
/** /**
* Sets the color Id of the author * Sets the color Id of the author
@ -156,7 +158,7 @@ exports.getAuthorColorId = function (author, callback)
exports.setAuthorColorId = function (author, colorId, callback) exports.setAuthorColorId = function (author, colorId, callback)
{ {
db.setSub("globalAuthor:" + author, ["colorId"], colorId, callback); db.setSub("globalAuthor:" + author, ["colorId"], colorId, callback);
} };
/** /**
* Returns the name of the author * Returns the name of the author
@ -166,7 +168,7 @@ exports.setAuthorColorId = function (author, colorId, callback)
exports.getAuthorName = function (author, callback) exports.getAuthorName = function (author, callback)
{ {
db.getSub("globalAuthor:" + author, ["name"], callback); db.getSub("globalAuthor:" + author, ["name"], callback);
} };
/** /**
* Sets the name of the author * Sets the name of the author
@ -176,12 +178,12 @@ exports.getAuthorName = function (author, callback)
exports.setAuthorName = function (author, name, callback) exports.setAuthorName = function (author, name, callback)
{ {
db.setSub("globalAuthor:" + author, ["name"], name, callback); db.setSub("globalAuthor:" + author, ["name"], name, callback);
} };
/** /**
* Generates a random String with the given length. Is needed to generate the Author Ids * 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 chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var randomstring = ''; var randomstring = '';

View file

@ -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 * provided by the settings module
*/ */
@ -33,14 +33,14 @@ exports.db = null;
/** /**
* Initalizes the database with the settings provided by the settings module * Initalizes the database with the settings provided by the settings module
* @param {Function} callback * @param {Function} callback
*/ */
exports.init = function(callback) exports.init = function(callback)
{ {
//initalize the database async //initalize the database async
db.init(function(err) 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) if(err)
{ {
console.error("ERROR: Problem while initalizing the database"); console.error("ERROR: Problem while initalizing the database");
@ -50,8 +50,8 @@ exports.init = function(callback)
//everything ok //everything ok
else else
{ {
exports.db = db; exports.db = db;
callback(null); callback(null);
} }
}); });
} };

View file

@ -17,29 +17,29 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var customError = require("../utils/customError"); var customError = require("../utils/customError");
var db = require("./DB").db; var db = require("./DB").db;
var async = require("async"); var async = require("async");
var padManager = require("./PadManager"); var padManager = require("./PadManager");
var sessionManager = require("./SessionManager"); var sessionManager = require("./SessionManager");
exports.deleteGroup = function(groupID, callback) exports.deleteGroup = function(groupID, callback)
{ {
var group; var group;
async.series([ async.series([
//ensure group exists //ensure group exists
function (callback) function (callback)
{ {
//try to get the group entry //try to get the group entry
db.get("group:" + groupID, function (err, _group) db.get("group:" + groupID, function (err, _group)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//group does not exist //group does not exist
if(_group == null) if(!_group)
{ {
callback(new customError("groupID does not exist","apierror")); callback(new customError("groupID does not exist","apierror"));
} }
@ -60,14 +60,14 @@ exports.deleteGroup = function(groupID, callback)
{ {
padIDs.push(i); padIDs.push(i);
} }
//loop trough all pads and delete them //loop trough all pads and delete them
async.forEach(padIDs, function(padID, callback) async.forEach(padIDs, function(padID, callback)
{ {
padManager.getPad(padID, function(err, pad) padManager.getPad(padID, function(err, pad)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
pad.remove(callback); pad.remove(callback);
}); });
}, callback); }, callback);
@ -79,18 +79,21 @@ exports.deleteGroup = function(groupID, callback)
db.get("group2sessions:" + groupID, function (err, group2sessions) db.get("group2sessions:" + groupID, function (err, group2sessions)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//skip if there is no group2sessions entry //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 //collect all sessions in an array, that allows us to use async.forEach
var sessions = []; var sessions = [];
for(var i in group2sessions.sessionsIDs) for(var i in group2sessions.sessionsIDs)
{ {
sessions.push(i); sessions.push(i);
} }
//loop trough all sessions and delete them //loop trough all sessions and delete them
async.forEach(sessions, function(session, callback) async.forEach(sessions, function(session, callback)
{ {
sessionManager.deleteSession(session, callback); sessionManager.deleteSession(session, callback);
@ -109,27 +112,27 @@ exports.deleteGroup = function(groupID, callback)
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
callback(); callback();
}); });
} };
exports.doesGroupExist = function(groupID, callback) exports.doesGroupExist = function(groupID, callback)
{ {
//try to get the group entry //try to get the group entry
db.get("group:" + groupID, function (err, group) db.get("group:" + groupID, function (err, group)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
callback(null, group != null); callback(null, group ? true : false);
}); });
} };
exports.createGroup = function(callback) exports.createGroup = function(callback)
{ {
//search for non existing groupID //search for non existing groupID
var groupID = "g." + randomString(16); var groupID = "g." + randomString(16);
//create the group //create the group
db.set("group:" + groupID, {pads: {}}); db.set("group:" + groupID, {pads: {}});
callback(null, {groupID: groupID}); callback(null, {groupID: groupID});
} };
exports.createGroupIfNotExistsFor = function(groupMapper, callback) exports.createGroupIfNotExistsFor = function(groupMapper, callback)
{ {
@ -139,22 +142,22 @@ exports.createGroupIfNotExistsFor = function(groupMapper, callback)
callback(new customError("groupMapper is no string","apierror")); callback(new customError("groupMapper is no string","apierror"));
return; return;
} }
//try to get a group for this mapper //try to get a group for this mapper
db.get("mapper2group:"+groupMapper, function(err, groupID) db.get("mapper2group:"+groupMapper, function(err, groupID)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//there is no group for this mapper, let's create a group //there is no group for this mapper, let's create a group
if(groupID == null) if(!groupID)
{ {
exports.createGroup(function(err, responseObj) exports.createGroup(function(err, responseObj)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//create the mapper entry for this group //create the mapper entry for this group
db.set("mapper2group:"+groupMapper, responseObj.groupID); db.set("mapper2group:"+groupMapper, responseObj.groupID);
callback(null, responseObj); callback(null, responseObj);
}); });
} }
@ -165,7 +168,7 @@ exports.createGroupIfNotExistsFor = function(groupMapper, callback)
callback(null, {groupID: groupID}); callback(null, {groupID: groupID});
} }
}); });
} };
exports.createGroupPad = function(groupID, padName, text, callback) exports.createGroupPad = function(groupID, padName, text, callback)
{ {
@ -173,15 +176,15 @@ exports.createGroupPad = function(groupID, padName, text, callback)
var padID = groupID + "$" + padName; var padID = groupID + "$" + padName;
async.series([ async.series([
//ensure group exists //ensure group exists
function (callback) function (callback)
{ {
exports.doesGroupExist(groupID, function(err, exists) exports.doesGroupExist(groupID, function(err, exists)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//group does not exist //group does not exist
if(exists == false) if(!exists)
{ {
callback(new customError("groupID does not exist","apierror")); 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) padManager.doesPadExists(padID, function(err, exists)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//pad exists already //pad exists already
if(exists == true) if(exists)
{ {
callback(new customError("padName does already exist","apierror")); callback(new customError("padName does already exist","apierror"));
} }
@ -231,16 +234,16 @@ exports.createGroupPad = function(groupID, padName, text, callback)
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
callback(null, {padID: padID}); callback(null, {padID: padID});
}); });
} };
exports.listPads = function(groupID, callback) exports.listPads = function(groupID, callback)
{ {
exports.doesGroupExist(groupID, function(err, exists) exports.doesGroupExist(groupID, function(err, exists)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//group does not exist //group does not exist
if(exists == false) if(!exists)
{ {
callback(new customError("groupID does not exist","apierror")); 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 * 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 chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var randomstring = ''; var randomstring = '';

View file

@ -2,8 +2,6 @@
* The pad object, defined with joose * The pad object, defined with joose
*/ */
require('joose');
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var Changeset = require("../utils/Changeset"); var Changeset = require("../utils/Changeset");
var AttributePoolFactory = require("../utils/AttributePoolFactory"); var AttributePoolFactory = require("../utils/AttributePoolFactory");
@ -15,497 +13,463 @@ var padManager = require("./PadManager");
var padMessageHandler = require("../handler/PadMessageHandler"); var padMessageHandler = require("../handler/PadMessageHandler");
var readOnlyManager = require("./ReadOnlyManager"); var readOnlyManager = require("./ReadOnlyManager");
var crypto = require("crypto"); var crypto = require("crypto");
var cleanText = require("../utils/cleantext").cleanText;
/** var Pad = function Pad(id) {
* Copied from the Etherpad source code. It converts Windows line breaks to Unix line breaks and convert Tabs to spaces this.atext = Changeset.makeAText("\n");
* @param txt this.pool = AttributePoolFactory.createAttributePool();
*/ this.head = -1;
exports.cleanText = function (txt) { this.chatHead = -1;
return txt.replace(/\r\n/g,'\n').replace(/\r/g,'\n').replace(/\t/g, ' ').replace(/\xa0/g, ' '); this.publicStatus = false;
} this.passwordHash = null;
this.id = id;
};
Class('Pad', { exports.Pad = Pad;
// these are the properties Pad.prototype.apool = function apool() {
has : { return this.pool;
};
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' }
},
methods : { Pad.prototype.getHeadRevisionNumber = function getHeadRevisionNumber() {
return this.head;
BUILD : function (id) };
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 { authors.push(this.pool.numToAttrib[key][1]);
'id' : id, }
} }
},
appendRevision : function(aChangeset, author)
{
if(!author)
author = '';
var newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool); return authors;
Changeset.copyAText(newAText, this.atext); };
var newRev = ++this.head; Pad.prototype.getInternalRevisionAText = function getInternalRevisionAText(targetRev, callback) {
var _this = this;
var newRevData = {};
newRevData.changeset = aChangeset; var keyRev = this.getKeyRevisionNumber(targetRev);
newRevData.meta = {}; var atext;
newRevData.meta.author = author; var changesets = [];
newRevData.meta.timestamp = new Date().getTime();
//find out which changesets are needed
//ex. getNumForAuthor var neededChangesets = [];
if(author != '') var curRev = keyRev;
this.pool.putAttrib(['author', author || '']); while (curRev < targetRev)
{
if(newRev % 100 == 0) curRev++;
{ neededChangesets.push(curRev);
newRevData.meta.atext = this.atext; }
}
async.series([
db.set("pad:"+this.id+":revs:"+newRev, newRevData); //get all needed data out of the database
db.set("pad:"+this.id, {atext: this.atext, function(callback)
pool: this.pool.toJsonable(),
head: this.head,
chatHead: this.chatHead,
publicStatus: this.publicStatus,
passwordHash: this.passwordHash});
}, //appendRevision
getRevisionChangeset : function(revNum, callback)
{ {
db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback); async.parallel([
}, // getRevisionChangeset //get the atext of the key revision
function (callback)
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] != "")
{ {
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);
} }
} ], callback);
return authors;
}, },
//apply all changesets to the key changeset
getInternalRevisionAText : function(targetRev, callback) function(callback)
{ {
var _this = this; var apool = _this.apool();
var keyRev = this.getKeyRevisionNumber(targetRev);
var atext;
var changesets = [];
//find out which changesets are needed
var neededChangesets = [];
var curRev = keyRev; var curRev = keyRev;
while (curRev < targetRev)
while (curRev < targetRev)
{ {
curRev++; curRev++;
neededChangesets.push(curRev); var cs = changesets[curRev];
atext = Changeset.applyToAText(cs, atext, apool);
} }
async.series([ callback(null);
//get all needed data out of the database }
function(callback) ], function(err)
{ {
async.parallel([ if(ERR(err, callback)) return;
//get the atext of the key revision callback(null, atext);
function (callback) });
{
db.getSub("pad:"+_this.id+":revs:"+keyRev, ["meta", "atext"], function(err, _atext) };
{
if(ERR(err, callback)) return; Pad.prototype.getKeyRevisionNumber = function getKeyRevisionNumber(revNum) {
atext = Changeset.cloneAText(_atext); return Math.floor(revNum / 100) * 100;
callback(); };
});
}, Pad.prototype.text = function text() {
//get all needed changesets return this.atext.text;
function (callback) };
{
async.forEach(neededChangesets, function(item, callback) Pad.prototype.setText = function setText(newText) {
{ //clean the new text
_this.getRevisionChangeset(item, function(err, changeset) newText = cleanText(newText);
{
if(ERR(err, callback)) return; var oldText = this.text();
changesets[item] = changeset;
callback(); //create the changeset
}); var changeset = Changeset.makeSplice(oldText, 0, oldText.length-1, newText);
}, callback);
} //append the changeset
], callback); this.appendRevision(changeset);
}, };
//apply all changesets to the key changeset
function(callback) Pad.prototype.appendChatMessage = function appendChatMessage(text, userId, time) {
{ this.chatHead++;
var apool = _this.apool(); //save the chat entry in the database
var curRev = keyRev; db.set("pad:"+this.id+":chat:"+this.chatHead, {"text": text, "userId": userId, "time": time});
//save the new chat head
while (curRev < targetRev) db.setSub("pad:"+this.id, ["chatHead"], this.chatHead);
{ };
curRev++;
var cs = changesets[curRev]; Pad.prototype.getChatMessage = function getChatMessage(entryNum, callback) {
atext = Changeset.applyToAText(cs, atext, apool); var _this = this;
} var entry;
callback(null); async.series([
} //get the chat entry
], function(err) function(callback)
{
db.get("pad:"+_this.id+":chat:"+entryNum, function(err, _entry)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
callback(null, atext); entry = _entry;
callback();
}); });
}, },
//add the authorName
getKeyRevisionNumber : function(revNum) function(callback)
{ {
return Math.floor(revNum / 100) * 100; //this chat message doesn't exist, return null
}, if(!entry)
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)
{ {
if(ERR(err, callback)) return; callback();
callback(null, entry);
});
},
getLastChatMessages: function(count, callback)
{
//return an empty array if there are no chat messages
if(this.chatHead == -1)
{
callback(null, []);
return; return;
} }
var _this = this;
//works only if we decrement the amount, for some reason
count--;
//set the startpoint //get the authorName
var start = this.chatHead-count; authorManager.getAuthorName(entry.userId, function(err, authorName)
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<entries.length;i++)
{
if(entries[i]!=null)
cleanedEntries.push(entries[i]);
else
console.warn("WARNING: Found broken chat entry in pad " + _this.id);
}
callback(null, cleanedEntries);
});
},
init : function (text, callback)
{
var _this = this;
//replace text with default text if text isn't set
if(text == null)
{
text = settings.defaultPadText;
}
//try to load the pad
db.get("pad:"+this.id, function(err, value)
{
if(ERR(err, callback)) return;
//if this pad exists, load it
if(value != null)
{
_this.head = value.head;
_this.atext = value.atext;
_this.pool = _this.pool.fromJsonable(value.pool);
//ensure we have a local chatHead variable
if(value.chatHead != null)
_this.chatHead = value.chatHead;
else
_this.chatHead = -1;
//ensure we have a local publicStatus variable
if(value.publicStatus != null)
_this.publicStatus = value.publicStatus;
else
_this.publicStatus = false;
//ensure we have a local passwordHash variable
if(value.passwordHash != null)
_this.passwordHash = value.passwordHash;
else
_this.passwordHash = null;
}
//this pad doesn't exist, so create it
else
{
var firstChangeset = Changeset.makeSplice("\n", 0, 0, exports.cleanText(text));
_this.appendRevision(firstChangeset, '');
}
callback(null);
});
},
remove: function(callback)
{
var padID = this.id;
var _this = this;
//kick everyone from this pad
padMessageHandler.kickSessionsFromPad(padID);
async.series([
//delete all relations
function(callback)
{
async.parallel([
//is it a group pad? -> 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; if(ERR(err, callback)) return;
entry.userName = authorName;
callback(); 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<entries.length;i++)
{
if(entries[i]) {
cleanedEntries.push(entries[i]);
}
else
console.warn("WARNING: Found broken chat entry in pad " + _this.id);
}
callback(null, cleanedEntries);
});
};
Pad.prototype.init = function init(text, callback) {
var _this = this;
//replace text with default text if text isn't set
if(!text)
{
text = settings.defaultPadText;
}
//try to load the pad
db.get("pad:"+this.id, function(err, value)
{
if(ERR(err, callback)) return;
//if this pad exists, load it
if(value)
{
_this.head = value.head;
_this.atext = value.atext;
_this.pool = _this.pool.fromJsonable(value.pool);
//ensure we have a local chatHead variable
if(value.chatHead) {
_this.chatHead = value.chatHead;
}
else {
_this.chatHead = -1;
}
//ensure we have a local publicStatus variable
if(value.publicStatus) {
_this.publicStatus = value.publicStatus;
}
else {
_this.publicStatus = false;
}
//ensure we have a local passwordHash variable
if(value.passwordHash) {
_this.passwordHash = value.passwordHash;
}
else {
_this.passwordHash = null;
}
}
//this pad doesn't exist, so create it
else
{
var firstChangeset = Changeset.makeSplice("\n", 0, 0, cleanText(text));
_this.appendRevision(firstChangeset, '');
}
callback(null);
});
};
Pad.prototype.remove = function remove(callback) {
var padID = this.id;
var _this = this;
//kick everyone from this pad
padMessageHandler.kickSessionsFromPad(padID);
async.series([
//delete all relations
function(callback)
{
async.parallel([
//is it a group pad? -> 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 */ /* Crypto helper methods */
@ -531,5 +495,5 @@ function generateSalt()
function compare(hashStr, password) function compare(hashStr, password)
{ {
return hash(password, hashStr.split("$")[1]) === hashStr; return hash(password, hashStr.split("$")[1]) === hashStr;
} }

View file

@ -20,10 +20,10 @@
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var customError = require("../utils/customError"); var customError = require("../utils/customError");
require("../db/Pad"); var Pad = require("../db/Pad").Pad;
var db = require("./DB").db; var db = require("./DB").db;
/** /**
* An Object containing all known Pads. Provides "get" and "set" functions, * An Object containing all known Pads. Provides "get" and "set" functions,
* which should be used instead of indexing with brackets. These prepend a * 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 * 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 * Returns a Pad Object with the callback
* @param id A String with the id of the pad * @param id A String with the id of the pad
* @param {Function} callback * @param {Function} callback
*/ */
exports.getPad = function(id, text, callback) exports.getPad = function(id, text, callback)
{ {
//check if this is a valid padId //check if this is a valid padId
if(!exports.isValidPadId(id)) if(!exports.isValidPadId(id))
{ {
callback(new customError(id + " is not a valid padId","apierror")); callback(new customError(id + " is not a valid padId","apierror"));
return; return;
} }
//make text an optional parameter //make text an optional parameter
if(typeof text == "function") if(typeof text === "function")
{ {
callback = text; callback = text;
text = null; text = null;
} }
//check if this is a valid text //check if this is a valid text
if(text != null) if(text)
{ {
//check if text is a string //check if text is a string
if(typeof text != "string") if(typeof text != "string")
@ -68,7 +68,7 @@ exports.getPad = function(id, text, callback)
callback(new customError("text is not a string","apierror")); callback(new customError("text is not a string","apierror"));
return; return;
} }
//check if text is less than 100k chars //check if text is less than 100k chars
if(text.length > 100000) if(text.length > 100000)
{ {
@ -76,11 +76,11 @@ exports.getPad = function(id, text, callback)
return; return;
} }
} }
var pad = globalPads.get(id); var pad = globalPads.get(id);
//return pad if its already loaded //return pad if its already loaded
if(pad != null) if(pad)
{ {
callback(null, pad); callback(null, pad);
} }
@ -88,17 +88,17 @@ exports.getPad = function(id, text, callback)
else else
{ {
pad = new Pad(id); pad = new Pad(id);
//initalize the pad //initalize the pad
pad.init(text, function(err) pad.init(text, function(err)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
globalPads.set(id, pad); globalPads.set(id, pad);
callback(null, pad); callback(null, pad);
}); });
} }
} };
//checks if a pad exists //checks if a pad exists
exports.doesPadExists = function(padId, callback) exports.doesPadExists = function(padId, callback)
@ -106,18 +106,18 @@ exports.doesPadExists = function(padId, callback)
db.get("pad:"+padId, function(err, value) db.get("pad:"+padId, function(err, value)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
callback(null, value != null); callback(null, value ? true : false);
}); });
} };
exports.isValidPadId = function(padId) 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 //removes a pad from the array
exports.unloadPad = function(padId) exports.unloadPad = function(padId)
{ {
if(globalPads.get(padId)) if(globalPads.get(padId))
globalPads.remove(padId); globalPads.remove(padId);
} };

View file

@ -27,9 +27,9 @@ var async = require("async");
* @param {String} padId the id of the pad * @param {String} padId the id of the pad
*/ */
exports.getReadOnlyId = function (padId, callback) exports.getReadOnlyId = function (padId, callback)
{ {
var readOnlyId; var readOnlyId;
async.waterfall([ async.waterfall([
//check if there is a pad2readonly entry //check if there is a pad2readonly entry
function(callback) function(callback)
@ -39,10 +39,10 @@ exports.getReadOnlyId = function (padId, callback)
function(dbReadOnlyId, callback) function(dbReadOnlyId, callback)
{ {
//there is no readOnly Entry in the database, let's create one //there is no readOnly Entry in the database, let's create one
if(dbReadOnlyId == null) if(!dbReadOnlyId)
{ {
readOnlyId = "r." + randomString(16); readOnlyId = "r." + randomString(16);
db.set("pad2readonly:" + padId, readOnlyId); db.set("pad2readonly:" + padId, readOnlyId);
db.set("readonly2pad:" + readOnlyId, padId); db.set("readonly2pad:" + readOnlyId, padId);
} }
@ -51,7 +51,7 @@ exports.getReadOnlyId = function (padId, callback)
{ {
readOnlyId = dbReadOnlyId; readOnlyId = dbReadOnlyId;
} }
callback(); callback();
} }
], function(err) ], function(err)
@ -59,8 +59,8 @@ exports.getReadOnlyId = function (padId, callback)
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//return the results //return the results
callback(null, readOnlyId); callback(null, readOnlyId);
}) });
} };
/** /**
* returns a the padId for a read only id * returns a the padId for a read only id
@ -69,12 +69,12 @@ exports.getReadOnlyId = function (padId, callback)
exports.getPadId = function(readOnlyId, callback) exports.getPadId = function(readOnlyId, callback)
{ {
db.get("readonly2pad:" + readOnlyId, callback); db.get("readonly2pad:" + readOnlyId, callback);
} };
/** /**
* Generates a random String with the given length. Is needed to generate the read only ids * 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 chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var randomstring = ''; var randomstring = '';

View file

@ -24,18 +24,18 @@ var async = require("async");
var authorManager = require("./AuthorManager"); var authorManager = require("./AuthorManager");
var padManager = require("./PadManager"); var padManager = require("./PadManager");
var sessionManager = require("./SessionManager"); 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. * 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 padID the pad the user wants to access
* @param sesssionID the session the user has (set via api) * @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 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}) * @param callback will be called with (err, {accessStatus: grant|deny|wrongPassword|needPassword, authorID: a.xxxxxx})
*/ */
exports.checkAccess = function (padID, sessionID, token, password, callback) exports.checkAccess = function (padID, sessionID, token, password, callback)
{ {
var statusObject; var statusObject;
// a valid session is required (api-only mode) // 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) authorManager.getAuthor4Token(token, function(err, author)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
// assume user has access // assume user has access
statusObject = {accessStatus: "grant", authorID: author}; statusObject = {accessStatus: "grant", authorID: author};
// user can't create pads // user can't create pads
@ -68,7 +68,7 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)
padManager.doesPadExists(padID, function(err, exists) padManager.doesPadExists(padID, function(err, exists)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
// pad doesn't exist - user can't have access // pad doesn't exist - user can't have access
if(!exists) statusObject.accessStatus = "deny"; if(!exists) statusObject.accessStatus = "deny";
// grant or deny access, with author of token // 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 // grant access, with author of token
callback(null, statusObject); callback(null, statusObject);
} }
}) });
//don't continue //don't continue
return; return;
} }
} }
var groupID = padID.split("$")[0]; var groupID = padID.split("$")[0];
var padExists = false; var padExists = false;
var validSession = false; var validSession = false;
@ -95,10 +95,10 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)
var tokenAuthor; var tokenAuthor;
var isPublic; var isPublic;
var isPasswordProtected; var isPasswordProtected;
var passwordStatus = password == null ? "notGiven" : "wrong"; // notGiven, correct, wrong var passwordStatus = password === null ? "notGiven" : "wrong"; // notGiven, correct, wrong
async.series([ async.series([
//get basic informations from the database //get basic informations from the database
function(callback) function(callback)
{ {
async.parallel([ async.parallel([
@ -123,19 +123,19 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)
callback(); callback();
return; return;
} }
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
var now = Math.floor(new Date().getTime()/1000); var now = Math.floor(new Date().getTime()/1000);
//is it for this group? and is validUntil still ok? --> validSession //is it for this group? and is validUntil still ok? --> validSession
if(sessionInfo.groupID == groupID && sessionInfo.validUntil > now) if(sessionInfo.groupID == groupID && sessionInfo.validUntil > now)
{ {
validSession = true; validSession = true;
} }
sessionAuthor = sessionInfo.authorID; sessionAuthor = sessionInfo.authorID;
callback(); callback();
}); });
}, },
@ -156,28 +156,28 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)
function(callback) function(callback)
{ {
//skip this if the pad doesn't exists //skip this if the pad doesn't exists
if(padExists == false) if(!padExists)
{ {
callback(); callback();
return; return;
} }
padManager.getPad(padID, function(err, pad) padManager.getPad(padID, function(err, pad)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//is it a public pad? //is it a public pad?
isPublic = pad.getPublicStatus(); isPublic = pad.getPublicStatus();
//is it password protected? //is it password protected?
isPasswordProtected = pad.isPasswordProtected(); isPasswordProtected = pad.isPasswordProtected();
//is password correct? //is password correct?
if(isPasswordProtected && password && pad.isCorrectPassword(password)) if(isPasswordProtected && password && pad.isCorrectPassword(password))
{ {
passwordStatus = "correct"; passwordStatus = "correct";
} }
callback(); callback();
}); });
}, },
@ -214,7 +214,7 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)
{ {
throw new Error("Ops, something wrong happend"); throw new Error("Ops, something wrong happend");
} }
} }
//- a valid session for this group avaible but pad doesn't exists //- a valid session for this group avaible but pad doesn't exists
else if(validSession && !padExists) else if(validSession && !padExists)
{ {
@ -238,7 +238,7 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)
//--> grant access, with author of token //--> grant access, with author of token
statusObject = {accessStatus: "grant", authorID: tokenAuthor}; 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") else if(isPublic && isPasswordProtected && passwordStatus == "wrong")
{ {
//--> deny access, ask for new password and tell them that the password is 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"); throw new Error("Ops, something wrong happend");
} }
} }
// there is no valid session avaiable AND pad doesn't exists // there is no valid session avaiable AND pad doesn't exists
else else
{ {
//--> deny access //--> deny access
statusObject = {accessStatus: "deny"}; statusObject = {accessStatus: "deny"};
} }
callback(); callback();
} }
], function(err) ], function(err)
@ -275,4 +275,4 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
callback(null, statusObject); callback(null, statusObject);
}); });
} };

View file

@ -17,24 +17,24 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var customError = require("../utils/customError"); var customError = require("../utils/customError");
var db = require("./DB").db; var db = require("./DB").db;
var async = require("async"); var async = require("async");
var groupMangager = require("./GroupManager"); var groupMangager = require("./GroupManager");
var authorMangager = require("./AuthorManager"); var authorMangager = require("./AuthorManager");
exports.doesSessionExist = function(sessionID, callback) exports.doesSessionExist = function(sessionID, callback)
{ {
//check if the database entry of this session exists //check if the database entry of this session exists
db.get("session:" + sessionID, function (err, session) db.get("session:" + sessionID, function (err, session)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
callback(null, session != null); callback(null, session ? true : false);
}); });
} };
/** /**
* Creates a new session between an author and a group * 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) groupMangager.doesGroupExist(groupID, function(err, exists)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//group does not exist //group does not exist
if(exists == false) if(!exists)
{ {
callback(new customError("groupID does not exist","apierror")); 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) authorMangager.doesAuthorExists(authorID, function(err, exists)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//author does not exist //author does not exist
if(exists == false) if(!exists)
{ {
callback(new customError("authorID does not exist","apierror")); callback(new customError("authorID does not exist","apierror"));
} }
@ -88,9 +88,9 @@ exports.createSession = function(groupID, authorID, validUntil, callback)
if(typeof validUntil != "number") if(typeof validUntil != "number")
{ {
//try to parse the number //try to parse the number
if(!isNaN(parseInt(validUntil))) if(!isNaN(parseInt(validUntil, 10)))
{ {
validUntil = parseInt(validUntil); validUntil = parseInt(validUntil, 10);
} }
else else
{ {
@ -98,34 +98,34 @@ exports.createSession = function(groupID, authorID, validUntil, callback)
return; return;
} }
} }
//ensure this is not a negativ number //ensure this is not a negativ number
if(validUntil < 0) if(validUntil < 0)
{ {
callback(new customError("validUntil is a negativ number","apierror")); callback(new customError("validUntil is a negativ number","apierror"));
return; return;
} }
//ensure this is not a float value //ensure this is not a float value
if(!is_int(validUntil)) if(!is_int(validUntil))
{ {
callback(new customError("validUntil is a float value","apierror")); callback(new customError("validUntil is a float value","apierror"));
return; return;
} }
//check if validUntil is in the future //check if validUntil is in the future
if(Math.floor(new Date().getTime()/1000) > validUntil) if(Math.floor(new Date().getTime()/1000) > validUntil)
{ {
callback(new customError("validUntil is in the past","apierror")); callback(new customError("validUntil is in the past","apierror"));
return; return;
} }
//generate sessionID //generate sessionID
sessionID = "s." + randomString(16); sessionID = "s." + randomString(16);
//set the session into the database //set the session into the database
db.set("session:" + sessionID, {"groupID": groupID, "authorID": authorID, "validUntil": validUntil}); db.set("session:" + sessionID, {"groupID": groupID, "authorID": authorID, "validUntil": validUntil});
callback(); callback();
}, },
//set the group2sessions entry //set the group2sessions entry
@ -135,19 +135,19 @@ exports.createSession = function(groupID, authorID, validUntil, callback)
db.get("group2sessions:" + groupID, function(err, group2sessions) db.get("group2sessions:" + groupID, function(err, group2sessions)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//the entry doesn't exist so far, let's create it //the entry doesn't exist so far, let's create it
if(group2sessions == null) if(!group2sessions)
{ {
group2sessions = {sessionIDs : {}}; group2sessions = {sessionIDs : {}};
} }
//add the entry for this session //add the entry for this session
group2sessions.sessionIDs[sessionID] = 1; group2sessions.sessionIDs[sessionID] = 1;
//save the new element back //save the new element back
db.set("group2sessions:" + groupID, group2sessions); db.set("group2sessions:" + groupID, group2sessions);
callback(); callback();
}); });
}, },
@ -158,30 +158,30 @@ exports.createSession = function(groupID, authorID, validUntil, callback)
db.get("author2sessions:" + authorID, function(err, author2sessions) db.get("author2sessions:" + authorID, function(err, author2sessions)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//the entry doesn't exist so far, let's create it //the entry doesn't exist so far, let's create it
if(author2sessions == null) if(!author2sessions)
{ {
author2sessions = {sessionIDs : {}}; author2sessions = {sessionIDs : {}};
} }
//add the entry for this session //add the entry for this session
author2sessions.sessionIDs[sessionID] = 1; author2sessions.sessionIDs[sessionID] = 1;
//save the new element back //save the new element back
db.set("author2sessions:" + authorID, author2sessions); db.set("author2sessions:" + authorID, author2sessions);
callback(); callback();
}); });
} }
], function(err) ], function(err)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//return error and sessionID //return error and sessionID
callback(null, {sessionID: sessionID}); callback(null, {sessionID: sessionID});
}) });
} };
exports.getSessionInfo = function(sessionID, callback) exports.getSessionInfo = function(sessionID, callback)
{ {
@ -189,11 +189,11 @@ exports.getSessionInfo = function(sessionID, callback)
db.get("session:" + sessionID, function (err, session) db.get("session:" + sessionID, function (err, session)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//session does not exists //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 //everything is fine, return the sessioninfos
else else
@ -201,7 +201,7 @@ exports.getSessionInfo = function(sessionID, callback)
callback(null, session); callback(null, session);
} }
}); });
} };
/** /**
* Deletes a session * Deletes a session
@ -218,18 +218,18 @@ exports.deleteSession = function(sessionID, callback)
db.get("session:" + sessionID, function (err, session) db.get("session:" + sessionID, function (err, session)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//session does not exists //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 //everything is fine, return the sessioninfos
else else
{ {
authorID = session.authorID; authorID = session.authorID;
groupID = session.groupID; groupID = session.groupID;
callback(); callback();
} }
}); });
@ -259,32 +259,32 @@ exports.deleteSession = function(sessionID, callback)
{ {
//remove the session //remove the session
db.remove("session:" + sessionID); db.remove("session:" + sessionID);
//remove session from group2sessions //remove session from group2sessions
delete group2sessions.sessionIDs[sessionID]; delete group2sessions.sessionIDs[sessionID];
db.set("group2sessions:" + groupID, group2sessions); db.set("group2sessions:" + groupID, group2sessions);
//remove session from author2sessions //remove session from author2sessions
delete author2sessions.sessionIDs[sessionID]; delete author2sessions.sessionIDs[sessionID];
db.set("author2sessions:" + authorID, author2sessions); db.set("author2sessions:" + authorID, author2sessions);
callback(); callback();
} }
], function(err) ], function(err)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
callback(); callback();
}) });
} };
exports.listSessionsOfGroup = function(groupID, callback) exports.listSessionsOfGroup = function(groupID, callback)
{ {
groupMangager.doesGroupExist(groupID, function(err, exists) groupMangager.doesGroupExist(groupID, function(err, exists)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//group does not exist //group does not exist
if(exists == false) if(!exists)
{ {
callback(new customError("groupID does not exist","apierror")); callback(new customError("groupID does not exist","apierror"));
} }
@ -294,16 +294,16 @@ exports.listSessionsOfGroup = function(groupID, callback)
listSessionsWithDBKey("group2sessions:" + groupID, callback); listSessionsWithDBKey("group2sessions:" + groupID, callback);
} }
}); });
} };
exports.listSessionsOfAuthor = function(authorID, callback) exports.listSessionsOfAuthor = function(authorID, callback)
{ {
authorMangager.doesAuthorExists(authorID, function(err, exists) authorMangager.doesAuthorExists(authorID, function(err, exists)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//group does not exist //group does not exist
if(exists == false) if(!exists)
{ {
callback(new customError("authorID does not exist","apierror")); callback(new customError("authorID does not exist","apierror"));
} }
@ -313,7 +313,7 @@ exports.listSessionsOfAuthor = function(authorID, callback)
listSessionsWithDBKey("author2sessions:" + authorID, callback); listSessionsWithDBKey("author2sessions:" + authorID, callback);
} }
}); });
} };
//this function is basicly the code listSessionsOfAuthor and listSessionsOfGroup has in common //this function is basicly the code listSessionsOfAuthor and listSessionsOfGroup has in common
function listSessionsWithDBKey (dbkey, callback) function listSessionsWithDBKey (dbkey, callback)
@ -332,14 +332,14 @@ function listSessionsWithDBKey (dbkey, callback)
}); });
}, },
function(callback) function(callback)
{ {
//collect all sessionIDs in an arrary //collect all sessionIDs in an arrary
var sessionIDs = []; var sessionIDs = [];
for (var i in sessions) for (var i in sessions)
{ {
sessionIDs.push(i); sessionIDs.push(i);
} }
//foreach trough the sessions and get the sessioninfos //foreach trough the sessions and get the sessioninfos
async.forEach(sessionIDs, function(sessionID, callback) 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 * Generates a random String with the given length. Is needed to generate the SessionIDs
*/ */
function randomString(len) function randomString(len)
{ {
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var randomstring = ''; var randomstring = '';
@ -375,6 +375,6 @@ function randomString(len)
//checks if a number is an int //checks if a number is an int
function is_int(value) function is_int(value)
{ {
return (parseFloat(value) == parseInt(value)) && !isNaN(value) return (parseFloat(value) == parseInt(value, 10)) && !isNaN(value);
} }

View file

@ -28,7 +28,7 @@ try
{ {
apikey = fs.readFileSync("../APIKEY.txt","utf8"); apikey = fs.readFileSync("../APIKEY.txt","utf8");
} }
catch(e) catch(e)
{ {
apikey = randomString(32); apikey = randomString(32);
fs.writeFileSync("../APIKEY.txt",apikey,"utf8"); fs.writeFileSync("../APIKEY.txt",apikey,"utf8");
@ -37,28 +37,28 @@ catch(e)
//a list of all functions //a list of all functions
var functions = { var functions = {
"createGroup" : [], "createGroup" : [],
"createGroupIfNotExistsFor" : ["groupMapper"], "createGroupIfNotExistsFor" : ["groupMapper"],
"deleteGroup" : ["groupID"], "deleteGroup" : ["groupID"],
"listPads" : ["groupID"], "listPads" : ["groupID"],
"createPad" : ["padID", "text"], "createPad" : ["padID", "text"],
"createGroupPad" : ["groupID", "padName", "text"], "createGroupPad" : ["groupID", "padName", "text"],
"createAuthor" : ["name"], "createAuthor" : ["name"],
"createAuthorIfNotExistsFor": ["authorMapper" , "name"], "createAuthorIfNotExistsFor": ["authorMapper" , "name"],
"createSession" : ["groupID", "authorID", "validUntil"], "createSession" : ["groupID", "authorID", "validUntil"],
"deleteSession" : ["sessionID"], "deleteSession" : ["sessionID"],
"getSessionInfo" : ["sessionID"], "getSessionInfo" : ["sessionID"],
"listSessionsOfGroup" : ["groupID"], "listSessionsOfGroup" : ["groupID"],
"listSessionsOfAuthor" : ["authorID"], "listSessionsOfAuthor" : ["authorID"],
"getText" : ["padID", "rev"], "getText" : ["padID", "rev"],
"setText" : ["padID", "text"], "setText" : ["padID", "text"],
"getHTML" : ["padID", "rev"], "getHTML" : ["padID", "rev"],
"setHTML" : ["padID", "html"], "setHTML" : ["padID", "html"],
"getRevisionsCount" : ["padID"], "getRevisionsCount" : ["padID"],
"deletePad" : ["padID"], "deletePad" : ["padID"],
"getReadOnlyID" : ["padID"], "getReadOnlyID" : ["padID"],
"setPublicStatus" : ["padID", "publicStatus"], "setPublicStatus" : ["padID", "publicStatus"],
"getPublicStatus" : ["padID"], "getPublicStatus" : ["padID"],
"setPassword" : ["padID", "password"], "setPassword" : ["padID", "password"],
"isPasswordProtected" : ["padID"] "isPasswordProtected" : ["padID"]
}; };
@ -72,12 +72,12 @@ var functions = {
exports.handle = function(functionName, fields, req, res) exports.handle = function(functionName, fields, req, res)
{ {
//check the api key! //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}); res.send({code: 4, message: "no or wrong API Key", data: null});
return; return;
} }
//check if this is a valid function name //check if this is a valid function name
var isKnownFunctionname = false; var isKnownFunctionname = false;
for(var knownFunctionname in functions) for(var knownFunctionname in functions)
@ -88,30 +88,30 @@ exports.handle = function(functionName, fields, req, res)
break; break;
} }
} }
//say goodbye if this is a unkown function //say goodbye if this is a unkown function
if(!isKnownFunctionname) if(!isKnownFunctionname)
{ {
res.send({code: 3, message: "no such function", data: null}); res.send({code: 3, message: "no such function", data: null});
return; return;
} }
//put the function parameters in an array //put the function parameters in an array
var functionParams = []; var functionParams = [];
for(var i=0;i<functions[functionName].length;i++) for(var i=0;i<functions[functionName].length;i++)
{ {
functionParams.push(fields[functions[functionName][i]]); functionParams.push(fields[functions[functionName][i]]);
} }
//add a callback function to handle the response //add a callback function to handle the response
functionParams.push(function(err, data) functionParams.push(function(err, data)
{ {
// no error happend, everything is fine // no error happend, everything is fine
if(err == null) if(!err)
{ {
if(!data) if(!data)
data = null; data = null;
res.send({code: 0, message: "ok", data: data}); res.send({code: 0, message: "ok", data: data});
} }
// parameters were wrong and the api stopped execution, pass the error // parameters were wrong and the api stopped execution, pass the error
@ -126,15 +126,15 @@ exports.handle = function(functionName, fields, req, res)
ERR(err); ERR(err);
} }
}); });
//call the api function //call the api function
api[functionName](functionParams[0],functionParams[1],functionParams[2],functionParams[3],functionParams[4]); api[functionName](functionParams[0],functionParams[1],functionParams[2],functionParams[3],functionParams[4]);
} };
/** /**
* Generates a random String with the given length. Is needed to generate the Author Ids * 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 chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var randomstring = ''; var randomstring = '';

View file

@ -28,39 +28,43 @@ var settings = require('../utils/Settings');
var os = require('os'); var os = require('os');
//load abiword only if its enabled //load abiword only if its enabled
if(settings.abiword != null) if(settings.abiword)
{
var abiword = require("../utils/Abiword"); var abiword = require("../utils/Abiword");
}
var tempDirectory = "/tmp"; var tempDirectory = "/tmp";
//tempDirectory changes if the operating system is windows //tempDirectory changes if the operating system is windows
if(os.type().indexOf("Windows") > -1) if(os.type().indexOf("Windows") > -1)
{ {
tempDirectory = process.env.TEMP; tempDirectory = process.env.TEMP;
} }
/** /**
* do a requested export * do a requested export
*/ */
exports.doExport = function(req, res, padId, type) exports.doExport = function(req, res, padId, type)
{ {
//tell the browser that this is a downloadable file //tell the browser that this is a downloadable file
res.attachment(padId + "." + type); res.attachment(padId + "." + type);
var randNum;
var srcFile;
var destFile;
//if this is a plain text export, we can do this directly //if this is a plain text export, we can do this directly
if(type == "txt") if(type == "txt")
{ {
padManager.getPad(padId, function(err, pad) padManager.getPad(padId, function(err, pad)
{ {
ERR(err); ERR(err);
res.send(pad.text()); res.send(pad.text());
}); });
} }
else if(type == 'dokuwiki') else if(type == 'dokuwiki')
{ {
var randNum;
var srcFile, destFile;
async.series([ async.series([
//render the dokuwiki document //render the dokuwiki document
@ -71,7 +75,7 @@ exports.doExport = function(req, res, padId, type)
res.send(dokuwiki); res.send(dokuwiki);
callback("stop"); callback("stop");
}); });
}, }
], function(err) ], function(err)
{ {
if(err && err != "stop") throw err; if(err && err != "stop") throw err;
@ -80,8 +84,6 @@ exports.doExport = function(req, res, padId, type)
else else
{ {
var html; var html;
var randNum;
var srcFile, destFile;
async.series([ async.series([
//render the html document //render the html document
@ -92,7 +94,7 @@ exports.doExport = function(req, res, padId, type)
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
html = _html; html = _html;
callback(); callback();
}); });
}, },
//decide what to do with the html export //decide what to do with the html export
function(callback) function(callback)
@ -101,13 +103,13 @@ exports.doExport = function(req, res, padId, type)
if(type == "html") if(type == "html")
{ {
res.send(html); res.send(html);
callback("stop"); callback("stop");
} }
else //write the html export to a file else //write the html export to a file
{ {
randNum = Math.floor(Math.random()*0xFFFFFFFF); randNum = Math.floor(Math.random()*0xFFFFFFFF);
srcFile = tempDirectory + "/eplite_export_" + randNum + ".html"; srcFile = tempDirectory + "/eplite_export_" + randNum + ".html";
fs.writeFile(srcFile, html, callback); fs.writeFile(srcFile, html, callback);
} }
}, },
//send the convert job to abiword //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 //ensure html can be collected by the garbage collector
html = null; html = null;
destFile = tempDirectory + "/eplite_export_" + randNum + "." + type; destFile = tempDirectory + "/eplite_export_" + randNum + "." + type;
abiword.convertFile(srcFile, destFile, type, callback); 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 //100ms delay to accomidate for slow windows fs
if(os.type().indexOf("Windows") > -1) if(os.type().indexOf("Windows") > -1)
{ {
setTimeout(function() setTimeout(function()
{ {
fs.unlink(destFile, callback); fs.unlink(destFile, callback);
}, 100); }, 100);
@ -152,6 +154,6 @@ exports.doExport = function(req, res, padId, type)
], function(err) ], function(err)
{ {
if(err && err != "stop") ERR(err); if(err && err != "stop") ERR(err);
}) });
} }
}; };

View file

@ -28,8 +28,10 @@ var formidable = require('formidable');
var os = require("os"); var os = require("os");
//load abiword only if its enabled //load abiword only if its enabled
if(settings.abiword != null) if(settings.abiword)
{
var abiword = require("../utils/Abiword"); var abiword = require("../utils/Abiword");
}
var tempDirectory = "/tmp/"; var tempDirectory = "/tmp/";
@ -38,20 +40,20 @@ if(os.type().indexOf("Windows") > -1)
{ {
tempDirectory = process.env.TEMP; tempDirectory = process.env.TEMP;
} }
/** /**
* do a requested import * do a requested import
*/ */
exports.doImport = function(req, res, padId) exports.doImport = function(req, res, padId)
{ {
//pipe to a file //pipe to a file
//convert file to text via abiword //convert file to text via abiword
//set text in the pad //set text in the pad
var srcFile, destFile; var srcFile, destFile;
var pad; var pad;
var text; var text;
async.series([ async.series([
//save the uploaded file to /tmp //save the uploaded file to /tmp
function(callback) function(callback)
@ -59,9 +61,9 @@ exports.doImport = function(req, res, padId)
var form = new formidable.IncomingForm(); var form = new formidable.IncomingForm();
form.keepExtensions = true; form.keepExtensions = true;
form.uploadDir = tempDirectory; form.uploadDir = tempDirectory;
form.parse(req, function(err, fields, files) form.parse(req, function(err, fields, files)
{ {
//the upload failed, stop at this point //the upload failed, stop at this point
if(err || files.file === undefined) if(err || files.file === undefined)
{ {
@ -69,7 +71,7 @@ exports.doImport = function(req, res, padId)
callback("uploadFailed"); callback("uploadFailed");
} }
//everything ok, continue //everything ok, continue
else else
{ {
//save the path of the uploaded file //save the path of the uploaded file
srcFile = files.file.path; 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 //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 //this allows us to accept source code files like .c or .java
function(callback) function(callback)
{ {
var fileEnding = srcFile.split(".")[1].toLowerCase(); var fileEnding = srcFile.split(".")[1].toLowerCase();
var knownFileEndings = ["txt", "doc", "docx", "pdf", "odt", "html", "htm"]; var knownFileEndings = ["txt", "doc", "docx", "pdf", "odt", "html", "htm"];
//find out if this is a known file ending //find out if this is a known file ending
var fileEndingKnown = false; var fileEndingKnown = false;
for(var i in knownFileEndings) for(var i in knownFileEndings)
@ -94,7 +96,7 @@ exports.doImport = function(req, res, padId)
fileEndingKnown = true; fileEndingKnown = true;
} }
} }
//if the file ending is known, continue as normal //if the file ending is known, continue as normal
if(fileEndingKnown) if(fileEndingKnown)
{ {
@ -105,11 +107,11 @@ exports.doImport = function(req, res, padId)
{ {
var oldSrcFile = srcFile; var oldSrcFile = srcFile;
srcFile = srcFile.split(".")[0] + ".txt"; srcFile = srcFile.split(".")[0] + ".txt";
fs.rename(oldSrcFile, srcFile, callback); fs.rename(oldSrcFile, srcFile, callback);
} }
}, },
//convert file to text //convert file to text
function(callback) function(callback)
{ {
@ -117,7 +119,7 @@ exports.doImport = function(req, res, padId)
destFile = tempDirectory + "eplite_import_" + randNum + ".txt"; destFile = tempDirectory + "eplite_import_" + randNum + ".txt";
abiword.convertFile(srcFile, destFile, "txt", callback); abiword.convertFile(srcFile, destFile, "txt", callback);
}, },
//get the pad object //get the pad object
function(callback) function(callback)
{ {
@ -128,7 +130,7 @@ exports.doImport = function(req, res, padId)
callback(); callback();
}); });
}, },
//read the text //read the text
function(callback) function(callback)
{ {
@ -136,30 +138,30 @@ exports.doImport = function(req, res, padId)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
text = _text; 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 //We add a 100ms delay to work around this
if(os.type().indexOf("Windows") > -1) if(os.type().indexOf("Windows") > -1)
{ {
setTimeout(function() setTimeout(function()
{ {
callback(); callback();
}, 100); }, 100);
} }
else else
{ {
callback(); callback();
} }
}); });
}, },
//change text of the pad and broadcast the changeset //change text of the pad and broadcast the changeset
function(callback) function(callback)
{ {
pad.setText(text); pad.setText(text);
padMessageHandler.updatePadClients(pad, callback); padMessageHandler.updatePadClients(pad, callback);
}, },
//clean up temporary files //clean up temporary files
function(callback) function(callback)
{ {
@ -184,8 +186,8 @@ exports.doImport = function(req, res, padId)
} }
ERR(err); ERR(err);
//close the connection //close the connection
res.send("ok"); res.send("ok");
}); });
} };

View file

@ -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) * Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
@ -61,18 +61,18 @@ var socketio;
exports.setSocketIO = function(socket_io) exports.setSocketIO = function(socket_io)
{ {
socketio=socket_io; socketio=socket_io;
} };
/** /**
* Handles the connection of a new user * Handles the connection of a new user
* @param client the new client * @param client the new client
*/ */
exports.handleConnect = function(client) exports.handleConnect = function(client)
{ {
//Initalize session2pad and sessioninfos for this new session //Initalize session2pad and sessioninfos for this new session
session2pad[client.id]=null; session2pad[client.id]=null;
sessioninfos[client.id]={}; sessioninfos[client.id]={};
} };
/** /**
* Kicks all sessions from a pad * Kicks all sessions from a pad
@ -89,27 +89,27 @@ exports.kickSessionsFromPad = function(padID)
{ {
socketio.sockets.sockets[pad2sessions[padID][i]].json.send({disconnect:"deleted"}); socketio.sockets.sockets[pad2sessions[padID][i]].json.send({disconnect:"deleted"});
} }
} };
/** /**
* Handles the disconnection of a user * Handles the disconnection of a user
* @param client the client that leaves * @param client the client that leaves
*/ */
exports.handleDisconnect = function(client) exports.handleDisconnect = function(client)
{ {
//save the padname of this session //save the padname of this session
var sessionPad=session2pad[client.id]; var sessionPad=session2pad[client.id];
//if this connection was already etablished with a handshake, send a disconnect message to the others //if this connection was already etablished with a handshake, send a disconnect message to the others
if(sessioninfos[client.id] && sessioninfos[client.id].author) if(sessioninfos[client.id] && sessioninfos[client.id].author)
{ {
var author = sessioninfos[client.id].author; var author = sessioninfos[client.id].author;
//get the author color out of the db //get the author color out of the db
authorManager.getAuthorColorId(author, function(err, color) authorManager.getAuthorColorId(author, function(err, color)
{ {
ERR(err); ERR(err);
//prepare the notification for the other users on the pad, that this user left //prepare the notification for the other users on the pad, that this user left
var messageToTheOtherUsers = { var messageToTheOtherUsers = {
"type": "COLLABROOM", "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 //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); socketio.sockets.sockets[pad2sessions[sessionPad][i]].json.send(messageToTheOtherUsers);
} }
}); });
} }
//Go trough all sessions of this pad, search and destroy the entry of this client //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) if(pad2sessions[sessionPad][i] == client.id)
{ {
pad2sessions[sessionPad].splice(i, 1); pad2sessions[sessionPad].splice(i, 1);
break; break;
} }
} }
//Delete the session2pad and sessioninfos entrys of this session //Delete the session2pad and sessioninfos entrys of this session
delete session2pad[client.id]; delete session2pad[client.id];
delete sessioninfos[client.id]; delete sessioninfos[client.id];
} };
/** /**
* Handles a message from a user * Handles a message from a user
@ -153,8 +153,8 @@ exports.handleDisconnect = function(client)
* @param message the message from the client * @param message the message from the client
*/ */
exports.handleMessage = function(client, message) exports.handleMessage = function(client, message)
{ {
if(message == null) if(!message)
{ {
messageLogger.warn("Message is null!"); messageLogger.warn("Message is null!");
return; return;
@ -164,39 +164,50 @@ exports.handleMessage = function(client, message)
messageLogger.warn("Message has no type attribute!"); messageLogger.warn("Message has no type attribute!");
return; return;
} }
//Check what type of message we get and delegate to the other methodes //Check what type of message we get and delegate to the other methodes
if(message.type == "CLIENT_READY") if(message.type == "CLIENT_READY")
{ {
handleClientReady(client, message); handleClientReady(client, message);
} }
else if(message.type == "COLLABROOM" && else if(message.type == "COLLABROOM" &&
message.data.type == "USER_CHANGES") message.data.type == "USER_CHANGES")
{ {
handleUserChanges(client, message); handleUserChanges(client, message);
} }
else if(message.type == "COLLABROOM" && else if(message.type == "COLLABROOM" &&
message.data.type == "USERINFO_UPDATE") message.data.type == "USERINFO_UPDATE")
{ {
handleUserInfoUpdate(client, message); handleUserInfoUpdate(client, message);
} }
else if(message.type == "COLLABROOM" && else if(message.type == "COLLABROOM" &&
message.data.type == "CHAT_MESSAGE") message.data.type == "CHAT_MESSAGE")
{ {
handleChatMessage(client, message); handleChatMessage(client, message);
} }
else if(message.type == "COLLABROOM" && else if(message.type == "COLLABROOM" &&
message.data.type == "CLIENT_MESSAGE" && message.data.type == "CLIENT_MESSAGE")
message.data.payload.type == "suggestUserName")
{ {
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 //if the message type is unknown, throw an exception
else else
{ {
messageLogger.warn("Dropped message, unknown Message Type " + message.type); messageLogger.warn("Dropped message, unknown Message Type " + message.type);
} }
} };
/** /**
* Handles a Chat Message * Handles a Chat Message
@ -209,10 +220,10 @@ function handleChatMessage(client, message)
var userId = sessioninfos[client.id].author; var userId = sessioninfos[client.id].author;
var text = message.data.text; var text = message.data.text;
var padId = session2pad[client.id]; var padId = session2pad[client.id];
var pad; var pad;
var userName; var userName;
async.series([ async.series([
//get the pad //get the pad
function(callback) function(callback)
@ -238,7 +249,7 @@ function handleChatMessage(client, message)
{ {
//save the chat message //save the chat message
pad.appendChatMessage(text, userId, time); pad.appendChatMessage(text, userId, time);
var msg = { var msg = {
type: "COLLABROOM", type: "COLLABROOM",
data: { data: {
@ -249,13 +260,13 @@ function handleChatMessage(client, message)
text: text text: text
} }
}; };
//broadcast the chat message to everyone on the pad //broadcast the chat message to everyone on the pad
for(var i in pad2sessions[padId]) for(var i in pad2sessions[padId])
{ {
socketio.sockets.sockets[pad2sessions[padId][i]].json.send(msg); socketio.sockets.sockets[pad2sessions[padId][i]].json.send(msg);
} }
callback(); callback();
} }
], function(err) ], function(err)
@ -273,19 +284,19 @@ function handleChatMessage(client, message)
function handleSuggestUserName(client, message) function handleSuggestUserName(client, message)
{ {
//check if all ok //check if all ok
if(message.data.payload.newName == null) if(!message.data.payload.newName)
{ {
messageLogger.warn("Dropped message, suggestUserName Message has no newName!"); messageLogger.warn("Dropped message, suggestUserName Message has no newName!");
return; return;
} }
if(message.data.payload.unnamedId == null) if(!message.data.payload.unnamedId)
{ {
messageLogger.warn("Dropped message, suggestUserName Message has no unnamedId!"); messageLogger.warn("Dropped message, suggestUserName Message has no unnamedId!");
return; return;
} }
var padId = session2pad[client.id]; var padId = session2pad[client.id];
//search the author and send him this message //search the author and send him this message
for(var i in pad2sessions[padId]) for(var i in pad2sessions[padId])
{ {
@ -305,30 +316,30 @@ function handleSuggestUserName(client, message)
function handleUserInfoUpdate(client, message) function handleUserInfoUpdate(client, message)
{ {
//check if all ok //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!"); messageLogger.warn("Dropped message, USERINFO_UPDATE Message has no colorId!");
return; return;
} }
//Find out the author name of this session //Find out the author name of this session
var author = sessioninfos[client.id].author; var author = sessioninfos[client.id].author;
//Tell the authorManager about the new attributes //Tell the authorManager about the new attributes
authorManager.setAuthorColorId(author, message.data.userInfo.colorId); authorManager.setAuthorColorId(author, message.data.userInfo.colorId);
authorManager.setAuthorName(author, message.data.userInfo.name); authorManager.setAuthorName(author, message.data.userInfo.name);
var padId = session2pad[client.id]; var padId = session2pad[client.id];
//set a null name, when there is no name set. cause the client wants it null //set a null name, when there is no name set. cause the client wants it null
if(message.data.userInfo.name == null) if(!message.data.userInfo.name)
{ {
message.data.userInfo.name = null; 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 //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"; message.data.type = "USER_NEWINFO";
//Send the other clients on the pad the update message //Send the other clients on the pad the update message
for(var i in pad2sessions[padId]) for(var i in pad2sessions[padId])
{ {
@ -349,29 +360,29 @@ function handleUserInfoUpdate(client, message)
function handleUserChanges(client, message) function handleUserChanges(client, message)
{ {
//check if all ok //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!"); messageLogger.warn("Dropped message, USER_CHANGES Message has no baseRev!");
return; return;
} }
if(message.data.apool == null) if(!message.data.apool)
{ {
messageLogger.warn("Dropped message, USER_CHANGES Message has no apool!"); messageLogger.warn("Dropped message, USER_CHANGES Message has no apool!");
return; return;
} }
if(message.data.changeset == null) if(!message.data.changeset)
{ {
messageLogger.warn("Dropped message, USER_CHANGES Message has no changeset!"); messageLogger.warn("Dropped message, USER_CHANGES Message has no changeset!");
return; return;
} }
//get all Vars we need //get all Vars we need
var baseRev = message.data.baseRev; var baseRev = message.data.baseRev;
var wireApool = (AttributePoolFactory.createAttributePool()).fromJsonable(message.data.apool); var wireApool = (AttributePoolFactory.createAttributePool()).fromJsonable(message.data.apool);
var changeset = message.data.changeset; var changeset = message.data.changeset;
var r, apool, pad; var r, apool, pad;
async.series([ async.series([
//get the pad //get the pad
function(callback) function(callback)
@ -387,13 +398,13 @@ function handleUserChanges(client, message)
function(callback) function(callback)
{ {
//ex. _checkChangesetAndPool //ex. _checkChangesetAndPool
//Copied from Etherpad, don't know what it does exactly //Copied from Etherpad, don't know what it does exactly
try try
{ {
//this looks like a changeset check, it throws errors sometimes //this looks like a changeset check, it throws errors sometimes
Changeset.checkRep(changeset); Changeset.checkRep(changeset);
Changeset.eachAttribNumber(changeset, function(n) { Changeset.eachAttribNumber(changeset, function(n) {
if (! wireApool.getAttrib(n)) { if (! wireApool.getAttrib(n)) {
throw "Attribute pool is missing attribute "+n+" for changeset "+changeset; throw "Attribute pool is missing attribute "+n+" for changeset "+changeset;
@ -407,27 +418,27 @@ function handleUserChanges(client, message)
client.json.send({disconnect:"badChangeset"}); client.json.send({disconnect:"badChangeset"});
return; return;
} }
//ex. adoptChangesetAttribs //ex. adoptChangesetAttribs
//Afaik, it copies the new attributes from the changeset, to the global Attribute Pool //Afaik, it copies the new attributes from the changeset, to the global Attribute Pool
changeset = Changeset.moveOpsToNewPool(changeset, wireApool, pad.pool); changeset = Changeset.moveOpsToNewPool(changeset, wireApool, pad.pool);
//ex. applyUserChanges //ex. applyUserChanges
apool = pad.pool; apool = pad.pool;
r = baseRev; r = baseRev;
//https://github.com/caolan/async#whilst //https://github.com/caolan/async#whilst
async.whilst( async.whilst(
function() { return r < pad.getHeadRevisionNumber(); }, function() { return r < pad.getHeadRevisionNumber(); },
function(callback) function(callback)
{ {
r++; r++;
pad.getRevisionChangeset(r, function(err, c) pad.getRevisionChangeset(r, function(err, c)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
changeset = Changeset.follow(c, changeset, false, apool); changeset = Changeset.follow(c, changeset, false, apool);
callback(null); callback(null);
}); });
@ -440,29 +451,29 @@ function handleUserChanges(client, message)
function (callback) function (callback)
{ {
var prevText = pad.text(); 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); console.warn("Can't apply USER_CHANGES "+changeset+" with oldLen " + Changeset.oldLen(changeset) + " to document of length " + prevText.length);
client.json.send({disconnect:"badChangeset"}); client.json.send({disconnect:"badChangeset"});
callback(); callback();
return; return;
} }
var thisAuthor = sessioninfos[client.id].author; var thisAuthor = sessioninfos[client.id].author;
pad.appendRevision(changeset, thisAuthor); pad.appendRevision(changeset, thisAuthor);
var correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool); var correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool);
if (correctionChangeset) { if (correctionChangeset) {
pad.appendRevision(correctionChangeset); pad.appendRevision(correctionChangeset);
} }
if (pad.text().lastIndexOf("\n\n") != pad.text().length-2) { if (pad.text().lastIndexOf("\n\n") != pad.text().length-2) {
var nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length-1, 0, "\n"); var nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length-1, 0, "\n");
pad.appendRevision(nlChangeset); pad.appendRevision(nlChangeset);
} }
exports.updatePadClients(pad, callback); exports.updatePadClients(pad, callback);
} }
], function(err) ], function(err)
@ -472,29 +483,29 @@ function handleUserChanges(client, message)
} }
exports.updatePadClients = function(pad, callback) exports.updatePadClients = function(pad, callback)
{ {
//skip this step if noone is on this pad //skip this step if noone is on this pad
if(!pad2sessions[pad.id]) if(!pad2sessions[pad.id])
{ {
callback(); callback();
return; return;
} }
//go trough all sessions on this pad //go trough all sessions on this pad
async.forEach(pad2sessions[pad.id], function(session, callback) async.forEach(pad2sessions[pad.id], function(session, callback)
{ {
var lastRev = sessioninfos[session].rev; var lastRev = sessioninfos[session].rev;
//https://github.com/caolan/async#whilst //https://github.com/caolan/async#whilst
//send them all new changesets //send them all new changesets
async.whilst( async.whilst(
function (){ return lastRev < pad.getHeadRevisionNumber()}, function (){ return lastRev < pad.getHeadRevisionNumber(); },
function(callback) function(callback)
{ {
var author, revChangeset; var author, revChangeset;
var r = ++lastRev; var r = ++lastRev;
async.parallel([ async.parallel([
function (callback) function (callback)
{ {
@ -517,7 +528,7 @@ exports.updatePadClients = function(pad, callback)
], function(err) ], function(err)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
if(author == sessioninfos[session].author) if(author == sessioninfos[session].author)
{ {
socketio.sockets.sockets[session].json.send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}}); 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, var wireMsg = {"type":"COLLABROOM","data":{type:"NEW_CHANGES", newRev:r,
changeset: forWire.translated, changeset: forWire.translated,
apool: forWire.pool, apool: forWire.pool,
author: author}}; author: author}};
socketio.sockets.sockets[session].json.send(wireMsg); socketio.sockets.sockets[session].json.send(wireMsg);
} }
callback(null); callback(null);
}); });
}, },
callback callback
); );
sessioninfos[session].rev = pad.getHeadRevisionNumber(); sessioninfos[session].rev = pad.getHeadRevisionNumber();
},callback); },callback);
} };
/** /**
* Copied from the Etherpad Source Code. Don't know what this methode does excatly... * 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; 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 * 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 client the client that send this message
* @param message the message from the client * @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) securityManager.checkAccess (message.padId, message.sessionID, message.token, message.password, function(err, statusObject)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//access was granted //access was granted
if(statusObject.accessStatus == "grant") if(statusObject.accessStatus == "grant")
{ {
@ -640,10 +651,10 @@ function handleClientReady(client, message)
//no access, send the client a message that tell him why //no access, send the client a message that tell him why
else else
{ {
client.json.send({accessStatus: statusObject.accessStatus}) client.json.send({accessStatus: statusObject.accessStatus});
} }
}); });
}, },
//get all authordata of this new user //get all authordata of this new user
function(callback) function(callback)
{ {
@ -692,7 +703,7 @@ function handleClientReady(client, message)
function(callback) function(callback)
{ {
var authors = pad.getAllAuthors(); var authors = pad.getAllAuthors();
async.parallel([ async.parallel([
//get all author data out of the database //get all author data out of the database
function(callback) function(callback)
@ -719,8 +730,8 @@ function handleClientReady(client, message)
}); });
} }
], callback); ], callback);
}, },
function(callback) function(callback)
{ {
@ -735,33 +746,33 @@ function handleClientReady(client, message)
} }
} }
} }
//Save in session2pad that this session belonges to this pad //Save in session2pad that this session belonges to this pad
var sessionId=String(client.id); var sessionId=String(client.id);
session2pad[sessionId] = message.padId; session2pad[sessionId] = message.padId;
//check if there is already a pad2sessions entry, if not, create one //check if there is already a pad2sessions entry, if not, create one
if(!pad2sessions[message.padId]) if(!pad2sessions[message.padId])
{ {
pad2sessions[message.padId] = []; pad2sessions[message.padId] = [];
} }
//Saves in pad2sessions that this session belongs to this pad //Saves in pad2sessions that this session belongs to this pad
pad2sessions[message.padId].push(sessionId); pad2sessions[message.padId].push(sessionId);
//prepare all values for the wire //prepare all values for the wire
var atext = Changeset.cloneAText(pad.atext); var atext = Changeset.cloneAText(pad.atext);
var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool); var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool);
var apool = attribsForWire.pool.toJsonable(); var apool = attribsForWire.pool.toJsonable();
atext.attribs = attribsForWire.translated; atext.attribs = attribsForWire.translated;
//check if abiword is avaiable //check if abiword is avaiable
var abiwordAvailable = settings.abiword != null ? "yes" : "no"; var abiwordAvailable = settings.abiword ? "yes" : "no";
if(settings.abiword != null && os.type().indexOf("Windows") != -1) if(settings.abiword && os.type().indexOf("Windows") != -1)
{ {
abiwordAvailable = "withoutPDF"; abiwordAvailable = "withoutPDF";
} }
var clientVars = { var clientVars = {
"accountPrivs": { "accountPrivs": {
"maxRevisions": 100 "maxRevisions": 100
@ -798,20 +809,20 @@ function handleClientReady(client, message)
"fullWidth": false, "fullWidth": false,
"hideSidebar": false "hideSidebar": false
}, },
"abiwordAvailable": abiwordAvailable, "abiwordAvailable": abiwordAvailable,
"hooks": {} "hooks": {}
} };
//Add a username to the clientVars if one avaiable //Add a username to the clientVars if one avaiable
if(authorName != null) if(authorName)
{ {
clientVars.userName = authorName; clientVars.userName = authorName;
} }
if(sessioninfos[client.id] !== undefined) if(sessioninfos[client.id] !== undefined)
{ {
//This is a reconnect, so we don't have to send the client the ClientVars again //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 //Save the revision in sessioninfos, we take the revision from the info the client send to us
sessioninfos[client.id].rev = message.client_rev; sessioninfos[client.id].rev = message.client_rev;
@ -824,11 +835,11 @@ function handleClientReady(client, message)
//Save the revision in sessioninfos //Save the revision in sessioninfos
sessioninfos[client.id].rev = pad.getHeadRevisionNumber(); sessioninfos[client.id].rev = pad.getHeadRevisionNumber();
} }
//Save the revision and the author id in sessioninfos //Save the revision and the author id in sessioninfos
sessioninfos[client.id].author = author; sessioninfos[client.id].author = author;
} }
//prepare the notification for the other users on the pad, that this user joined //prepare the notification for the other users on the pad, that this user joined
var messageToTheOtherUsers = { var messageToTheOtherUsers = {
"type": "COLLABROOM", "type": "COLLABROOM",
@ -842,18 +853,18 @@ function handleClientReady(client, message)
} }
} }
}; };
//Add the authorname of this new User, if avaiable //Add the authorname of this new User, if avaiable
if(authorName != null) if(authorName)
{ {
messageToTheOtherUsers.data.userInfo.name = authorName; messageToTheOtherUsers.data.userInfo.name = authorName;
} }
//Run trough all sessions of this pad //Run trough all sessions of this pad
async.forEach(pad2sessions[message.padId], function(sessionID, callback) async.forEach(pad2sessions[message.padId], function(sessionID, callback)
{ {
var sessionAuthorName, sessionAuthorColorId; var sessionAuthorName, sessionAuthorColorId;
async.series([ async.series([
//get the authorname & colorId //get the authorname & colorId
function(callback) function(callback)
@ -866,7 +877,7 @@ function handleClientReady(client, message)
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
sessionAuthorColorId = value; sessionAuthorColorId = value;
callback(); callback();
}) });
}, },
function(callback) function(callback)
{ {
@ -875,10 +886,10 @@ function handleClientReady(client, message)
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
sessionAuthorName = value; sessionAuthorName = value;
callback(); callback();
}) });
} }
],callback); ],callback);
}, },
function (callback) function (callback)
{ {
//Jump over, if this session is the connection session //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 //Send this Session the Notification about the new user
socketio.sockets.sockets[sessionID].json.send(messageToTheOtherUsers); socketio.sockets.sockets[sessionID].json.send(messageToTheOtherUsers);
//Send the new User a Notification about this other user //Send the new User a Notification about this other user
var messageToNotifyTheClientAboutTheOthers = { var messageToNotifyTheClientAboutTheOthers = {
"type": "COLLABROOM", "type": "COLLABROOM",
@ -904,7 +915,7 @@ function handleClientReady(client, message)
client.json.send(messageToNotifyTheClientAboutTheOthers); client.json.send(messageToNotifyTheClientAboutTheOthers);
} }
} }
], callback); ], callback);
}, callback); }, callback);
} }
],function(err) ],function(err)

View file

@ -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 * 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 * Saves all components
* key is the component name * key is the component name
* value is the component module * value is the component module
*/ */
var components = {}; var components = {};
var socket; var socket;
/** /**
* adds a component * adds a component
*/ */
@ -40,10 +40,10 @@ exports.addComponent = function(moduleName, module)
{ {
//save the component //save the component
components[moduleName] = module; components[moduleName] = module;
//give the module the socket //give the module the socket
module.setSocketIO(socket); module.setSocketIO(socket);
} };
/** /**
* sets the socket.io and adds event functions for routing * sets the socket.io and adds event functions for routing
@ -52,31 +52,31 @@ exports.setSocketIO = function(_socket)
{ {
//save this socket internaly //save this socket internaly
socket = _socket; socket = _socket;
socket.sockets.on('connection', function(client) socket.sockets.on('connection', function(client)
{ {
var clientAuthorized = false; var clientAuthorized = false;
//wrap the original send function to log the messages //wrap the original send function to log the messages
client._send = client.send; client._send = client.send;
client.send = function(message) client.send = function(message)
{ {
messageLogger.info("to " + client.id + ": " + stringifyWithoutPassword(message)); messageLogger.info("to " + client.id + ": " + stringifyWithoutPassword(message));
client._send(message); client._send(message);
} };
//tell all components about this connect //tell all components about this connect
for(var i in components) for(var i in components)
{ {
components[i].handleConnect(client); components[i].handleConnect(client);
} }
//try to handle the message of this client //try to handle the message of this client
function handleMessage(message) function handleMessage(message)
{ {
if(message.component && components[message.component]) 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]) if(components[message.component])
{ {
messageLogger.info("from " + client.id + ": " + stringifyWithoutPassword(message)); messageLogger.info("from " + client.id + ": " + stringifyWithoutPassword(message));
@ -87,8 +87,8 @@ exports.setSocketIO = function(_socket)
{ {
messageLogger.error("Can't route the message:" + stringifyWithoutPassword(message)); messageLogger.error("Can't route the message:" + stringifyWithoutPassword(message));
} }
} }
client.on('message', function(message) client.on('message', function(message)
{ {
if(message.protocolVersion && message.protocolVersion != 2) 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) securityManager.checkAccess (message.padId, message.sessionID, message.token, message.password, function(err, statusObject)
{ {
ERR(err); ERR(err);
//access was granted, mark the client as authorized and handle the message //access was granted, mark the client as authorized and handle the message
if(statusObject.accessStatus == "grant") if(statusObject.accessStatus == "grant")
{ {
@ -143,21 +143,21 @@ exports.setSocketIO = function(_socket)
} }
}); });
}); });
} };
//returns a stringified representation of a message, removes the password //returns a stringified representation of a message, removes the password
//this ensures there are no passwords in the log //this ensures there are no passwords in the log
function stringifyWithoutPassword(message) function stringifyWithoutPassword(message)
{ {
var newMessage = {}; var newMessage = {};
for(var i in message) for(var i in message)
{ {
if(i == "password" && message[i] != null) if(i == "password" && message[i])
newMessage["password"] = "xxx"; newMessage.password = "xxx";
else else
newMessage[i]=message[i]; newMessage[i]=message[i];
} }
return JSON.stringify(newMessage); return JSON.stringify(newMessage);
} }

View file

@ -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) * Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
@ -39,7 +39,7 @@ var socketio;
exports.setSocketIO = function(socket_io) exports.setSocketIO = function(socket_io)
{ {
socketio=socket_io; socketio=socket_io;
} };
/** /**
* Handles the connection of a new user * Handles the connection of a new user
@ -48,7 +48,7 @@ exports.setSocketIO = function(socket_io)
exports.handleConnect = function(client) exports.handleConnect = function(client)
{ {
} };
/** /**
* Handles the disconnection of a user * Handles the disconnection of a user
@ -56,8 +56,8 @@ exports.handleConnect = function(client)
*/ */
exports.handleDisconnect = function(client) exports.handleDisconnect = function(client)
{ {
} };
/** /**
* Handles a message from a user * Handles a message from a user
@ -65,7 +65,7 @@ exports.handleDisconnect = function(client)
* @param message the message from the client * @param message the message from the client
*/ */
exports.handleMessage = function(client, message) exports.handleMessage = function(client, message)
{ {
//Check what type of message we get and delegate to the other methodes //Check what type of message we get and delegate to the other methodes
if(message.type == "CLIENT_READY") if(message.type == "CLIENT_READY")
{ {
@ -80,70 +80,70 @@ exports.handleMessage = function(client, message)
{ {
messageLogger.warn("Dropped message, unknown Message Type: '" + message.type + "'"); messageLogger.warn("Dropped message, unknown Message Type: '" + message.type + "'");
} }
} };
function handleClientReady(client, message) function handleClientReady(client, message)
{ {
if(message.padId == null) if(!message.padId)
{ {
messageLogger.warn("Dropped message, changeset request has no padId!"); messageLogger.warn("Dropped message, changeset request has no padId!");
return; return;
} }
//send the timeslider client the clientVars, with this values its able to start //send the timeslider client the clientVars, with this values its able to start
createTimesliderClientVars (message.padId, function(err, clientVars) createTimesliderClientVars (message.padId, function(err, clientVars)
{ {
ERR(err); ERR(err);
client.json.send({type: "CLIENT_VARS", data: clientVars}); 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) function handleChangesetRequest(client, message)
{ {
//check if all ok //check if all ok
if(message.data == null) if(!message.data)
{ {
messageLogger.warn("Dropped message, changeset request has no data!"); messageLogger.warn("Dropped message, changeset request has no data!");
return; return;
} }
if(message.padId == null) if(!message.padId)
{ {
messageLogger.warn("Dropped message, changeset request has no padId!"); messageLogger.warn("Dropped message, changeset request has no padId!");
return; return;
} }
if(message.data.granularity == null) if(!message.data.granularity)
{ {
messageLogger.warn("Dropped message, changeset request has no granularity!"); messageLogger.warn("Dropped message, changeset request has no granularity!");
return; return;
} }
if(message.data.start == null) if(!message.data.start)
{ {
messageLogger.warn("Dropped message, changeset request has no start!"); messageLogger.warn("Dropped message, changeset request has no start!");
return; return;
} }
if(message.data.requestID == null) if(!message.data.requestID)
{ {
messageLogger.warn("Dropped message, changeset request has no requestID!"); messageLogger.warn("Dropped message, changeset request has no requestID!");
return; return;
} }
var granularity = message.data.granularity; var granularity = message.data.granularity;
var start = message.data.start; var start = message.data.start;
var end = start + (100 * granularity); var end = start + (100 * granularity);
var padId = message.padId; var padId = message.padId;
//build the requested rough changesets and send them back //build the requested rough changesets and send them back
getChangesetInfo(padId, start, end, granularity, function(err, changesetInfo) getChangesetInfo(padId, start, end, granularity, function(err, changesetInfo)
{ {
ERR(err); ERR(err);
var data = changesetInfo; var data = changesetInfo;
data.requestID = message.data.requestID; data.requestID = message.data.requestID;
client.json.send({type: "CHANGESET_REQ", data: data}); client.json.send({type: "CHANGESET_REQ", data: data});
}); });
} }
@ -171,19 +171,19 @@ function createTimesliderClientVars (padId, callback)
function(callback) function(callback)
{ {
padManager.getPad(padId, function(err, _pad) padManager.getPad(padId, function(err, _pad)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
pad = _pad; pad = _pad;
callback(); callback();
}); });
}, },
//get all authors and add them to //get all authors and add them to
function(callback) function(callback)
{ {
var historicalAuthorData = {}; var historicalAuthorData = {};
//get all authors out of the attribut pool //get all authors out of the attribut pool
var authors = pad.getAllAuthors(); var authors = pad.getAllAuthors();
//get all author data out of the database //get all author data out of the database
async.forEach(authors, function(authorId, callback) async.forEach(authors, function(authorId, callback)
{ {
@ -216,28 +216,28 @@ function createTimesliderClientVars (padId, callback)
{ {
//get the head revision Number //get the head revision Number
var lastRev = pad.getHeadRevisionNumber(); var lastRev = pad.getHeadRevisionNumber();
//add the revNum to the client Vars //add the revNum to the client Vars
clientVars.revNum = lastRev; clientVars.revNum = lastRev;
clientVars.totalRevs = lastRev; clientVars.totalRevs = lastRev;
var atext = Changeset.cloneAText(pad.atext); var atext = Changeset.cloneAText(pad.atext);
var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool); var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool);
var apool = attribsForWire.pool.toJsonable(); var apool = attribsForWire.pool.toJsonable();
atext.attribs = attribsForWire.translated; atext.attribs = attribsForWire.translated;
clientVars.initialStyledContents.apool = apool; clientVars.initialStyledContents.apool = apool;
clientVars.initialStyledContents.atext = atext; clientVars.initialStyledContents.atext = atext;
var granularities = [100, 10, 1]; var granularities = [100, 10, 1];
//get the latest rough changesets //get the latest rough changesets
async.forEach(granularities, function(granularity, callback) async.forEach(granularities, function(granularity, callback)
{ {
var topGranularity = granularity*10; var topGranularity = granularity*10;
getChangesetInfo(padId, Math.floor(lastRev / topGranularity)*topGranularity, getChangesetInfo(padId, Math.floor(lastRev / topGranularity)*topGranularity,
Math.floor(lastRev / topGranularity)*topGranularity+topGranularity, granularity, Math.floor(lastRev / topGranularity)*topGranularity+topGranularity, granularity,
function(err, changeset) function(err, changeset)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
@ -267,47 +267,47 @@ function getChangesetInfo(padId, startNum, endNum, granularity, callback)
var composedChangesets = {}; var composedChangesets = {};
var revisionDate = []; var revisionDate = [];
var lines; var lines;
async.series([ async.series([
//get the pad from the database //get the pad from the database
function(callback) function(callback)
{ {
padManager.getPad(padId, function(err, _pad) padManager.getPad(padId, function(err, _pad)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
pad = _pad; pad = _pad;
callback(); callback();
}); });
}, },
function(callback) function(callback)
{ {
//calculate the last full endnum //calculate the last full endnum
var lastRev = pad.getHeadRevisionNumber(); var lastRev = pad.getHeadRevisionNumber();
if (endNum > lastRev+1) { if (endNum > lastRev+1) {
endNum = lastRev+1; endNum = lastRev+1;
} }
endNum = Math.floor(endNum / granularity)*granularity; endNum = Math.floor(endNum / granularity)*granularity;
var compositesChangesetNeeded = []; var compositesChangesetNeeded = [];
var revTimesNeeded = []; var revTimesNeeded = [];
//figure out which composite Changeset and revTimes we need, to load them in bulk //figure out which composite Changeset and revTimes we need, to load them in bulk
var compositeStart = startNum; var compositeStart = startNum;
while (compositeStart < endNum) while (compositeStart < endNum)
{ {
var compositeEnd = compositeStart + granularity; var compositeEnd = compositeStart + granularity;
//add the composite Changeset we needed //add the composite Changeset we needed
compositesChangesetNeeded.push({start: compositeStart, end: compositeEnd}); compositesChangesetNeeded.push({start: compositeStart, end: compositeEnd});
//add the t1 time we need //add the t1 time we need
revTimesNeeded.push(compositeStart == 0 ? 0 : compositeStart - 1); revTimesNeeded.push(compositeStart === 0 ? 0 : compositeStart - 1);
//add the t2 time we need //add the t2 time we need
revTimesNeeded.push(compositeEnd - 1); revTimesNeeded.push(compositeEnd - 1);
compositeStart += granularity; compositeStart += granularity;
} }
//get all needed db values parallel //get all needed db values parallel
async.parallel([ async.parallel([
function(callback) function(callback)
@ -344,58 +344,58 @@ function getChangesetInfo(padId, startNum, endNum, granularity, callback)
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
lines = _lines; lines = _lines;
callback(); callback();
}); });
} }
], callback); ], callback);
}, },
//doesn't know what happens here excatly :/ //doesn't know what happens here excatly :/
function(callback) function(callback)
{ {
var compositeStart = startNum; var compositeStart = startNum;
while (compositeStart < endNum) while (compositeStart < endNum)
{ {
if (compositeStart + granularity > endNum) if (compositeStart + granularity > endNum)
{ {
break; break;
} }
var compositeEnd = compositeStart + granularity; var compositeEnd = compositeStart + granularity;
var forwards = composedChangesets[compositeStart + "/" + compositeEnd]; var forwards = composedChangesets[compositeStart + "/" + compositeEnd];
var backwards = Changeset.inverse(forwards, lines.textlines, lines.alines, pad.apool()); var backwards = Changeset.inverse(forwards, lines.textlines, lines.alines, pad.apool());
Changeset.mutateAttributionLines(forwards, lines.alines, pad.apool()); Changeset.mutateAttributionLines(forwards, lines.alines, pad.apool());
Changeset.mutateTextLines(forwards, lines.textlines); Changeset.mutateTextLines(forwards, lines.textlines);
var forwards2 = Changeset.moveOpsToNewPool(forwards, pad.apool(), apool); var forwards2 = Changeset.moveOpsToNewPool(forwards, pad.apool(), apool);
var backwards2 = Changeset.moveOpsToNewPool(backwards, pad.apool(), apool); var backwards2 = Changeset.moveOpsToNewPool(backwards, pad.apool(), apool);
var t1, t2; var t1, t2;
if (compositeStart == 0) if (compositeStart === 0)
{ {
t1 = revisionDate[0]; t1 = revisionDate[0];
} }
else else
{ {
t1 = revisionDate[compositeStart - 1]; t1 = revisionDate[compositeStart - 1];
} }
t2 = revisionDate[compositeEnd - 1]; t2 = revisionDate[compositeEnd - 1];
timeDeltas.push(t2 - t1); timeDeltas.push(t2 - t1);
forwardsChangesets.push(forwards2); forwardsChangesets.push(forwards2);
backwardsChangesets.push(backwards2); backwardsChangesets.push(backwards2);
compositeStart += granularity; compositeStart += granularity;
} }
callback(); callback();
} }
], function(err) ], function(err)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
callback(null, {forwardsChangesets: forwardsChangesets, callback(null, {forwardsChangesets: forwardsChangesets,
backwardsChangesets: backwardsChangesets, backwardsChangesets: backwardsChangesets,
apool: apool.toJsonable(), apool: apool.toJsonable(),
@ -410,7 +410,7 @@ function getChangesetInfo(padId, startNum, endNum, granularity, callback)
* Tries to rebuild the getPadLines function of the original Etherpad * 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 * 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 atext;
var result = {}; var result = {};
@ -421,7 +421,7 @@ function getPadLines(padId, revNum, callback)
function(callback) function(callback)
{ {
padManager.getPad(padId, function(err, _pad) padManager.getPad(padId, function(err, _pad)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
pad = _pad; pad = _pad;
callback(); callback();
@ -473,7 +473,7 @@ function composePadChangesets(padId, startNum, endNum, callback)
function(callback) function(callback)
{ {
padManager.getPad(padId, function(err, _pad) padManager.getPad(padId, function(err, _pad)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
pad = _pad; pad = _pad;
callback(); callback();
@ -483,14 +483,14 @@ function composePadChangesets(padId, startNum, endNum, callback)
function(callback) function(callback)
{ {
var changesetsNeeded=[]; 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 //replace the values with the changeset later
for(var r=startNum;r<endNum;r++) for(var r=startNum;r<endNum;r++)
{ {
changesetsNeeded.push(r); changesetsNeeded.push(r);
} }
//get all changesets //get all changesets
async.forEach(changesetsNeeded, function(revNum,callback) async.forEach(changesetsNeeded, function(revNum,callback)
{ {
@ -507,13 +507,13 @@ function composePadChangesets(padId, startNum, endNum, callback)
{ {
changeset = changesets[startNum]; changeset = changesets[startNum];
var pool = pad.apool(); var pool = pad.apool();
for(var r=startNum+1;r<endNum;r++) for(var r=startNum+1;r<endNum;r++)
{ {
var cs = changesets[r]; var cs = changesets[r];
changeset = Changeset.compose(changeset, cs, pool); changeset = Changeset.compose(changeset, cs, pool);
} }
callback(null); callback(null);
} }
], ],

View file

@ -1,6 +1,6 @@
/** /**
* This module is started with bin/run.sh. It sets up a Express HTTP and a Socket.IO Server. * This module is started with bin/run.sh. It sets up a Express HTTP and a Socket.IO Server.
* Static file Requests are answered directly from this module, Socket.IO messages are passed * Static file Requests are answered directly from this module, Socket.IO messages are passed
* to MessageHandler and minfied requests are passed to minified. * to MessageHandler and minfied requests are passed to minified.
*/ */
@ -51,12 +51,12 @@ try
version = version.substring(0, 7); version = version.substring(0, 7);
console.log("Your Etherpad Lite git version is " + version); console.log("Your Etherpad Lite git version is " + version);
} }
catch(e) catch(e)
{ {
console.warn("Can't get git version for server header\n" + e.message) console.warn("Can't get git version for server header\n" + e.message);
} }
console.log("Report bugs at https://github.com/Pita/etherpad-lite/issues") console.log("Report bugs at https://github.com/Pita/etherpad-lite/issues");
var serverName = "Etherpad-Lite " + version + " (http://j.mp/ep-lite)"; var serverName = "Etherpad-Lite " + version + " (http://j.mp/ep-lite)";
@ -77,7 +77,7 @@ async.waterfall([
{ {
//create server //create server
var app = express.createServer(); var app = express.createServer();
//load modules that needs a initalized db //load modules that needs a initalized db
readOnlyManager = require("./db/ReadOnlyManager"); readOnlyManager = require("./db/ReadOnlyManager");
exporthtml = require("./utils/ExportHtml"); exporthtml = require("./utils/ExportHtml");
@ -87,43 +87,46 @@ async.waterfall([
padManager = require('./db/PadManager'); padManager = require('./db/PadManager');
securityManager = require('./db/SecurityManager'); securityManager = require('./db/SecurityManager');
socketIORouter = require("./handler/SocketIORouter"); socketIORouter = require("./handler/SocketIORouter");
//install logging //install logging
var httpLogger = log4js.getLogger("http"); var httpLogger = log4js.getLogger("http");
app.configure(function() app.configure(function()
{ {
// Activate http basic auth if it has been defined in settings.json // Activate http basic auth if it has been defined in settings.json
if(settings.httpAuth != null) app.use(basic_auth); if(settings.httpAuth) {
app.use(basic_auth);
}
// If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158. // If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158.
// Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway. // Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway.
if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR")) if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR")) {
app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'})); app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'}));
}
app.use(express.cookieParser()); app.use(express.cookieParser());
}); });
app.error(function(err, req, res, next){ app.error(function(err, req, res, next){
res.send(500); res.send(500);
console.error(err.stack ? err.stack : err.toString()); console.error(err.stack ? err.stack : err.toString());
gracefulShutdown(); gracefulShutdown();
}); });
//serve static files //serve static files
app.get('/static/*', function(req, res) app.get('/static/*', function(req, res)
{ {
res.header("Server", serverName); res.header("Server", serverName);
var filePath = path.normalize(__dirname + "/.." + var filePath = path.normalize(__dirname + "/.." +
req.url.replace(/\.\./g, '').split("?")[0]); req.url.replace(/\.\./g, '').split("?")[0]);
res.sendfile(filePath, { maxAge: exports.maxAge }); res.sendfile(filePath, { maxAge: exports.maxAge });
}); });
//serve minified files //serve minified files
app.get('/minified/:id', function(req, res, next) app.get('/minified/:id', function(req, res, next)
{ {
res.header("Server", serverName); res.header("Server", serverName);
var id = req.params.id; var id = req.params.id;
if(id == "pad.js" || id == "timeslider.js") if(id == "pad.js" || id == "timeslider.js")
{ {
minify.minifyJS(req,res,id); minify.minifyJS(req,res,id);
@ -133,14 +136,15 @@ async.waterfall([
next(); next();
} }
}); });
//checks for padAccess //checks for padAccess
function hasPadAccess(req, res, callback) function hasPadAccess(req, res, callback)
{ {
console.log(req.params);
securityManager.checkAccess(req.params.pad, req.cookies.sessionid, req.cookies.token, req.cookies.password, function(err, accessObj) securityManager.checkAccess(req.params.pad, req.cookies.sessionid, req.cookies.token, req.cookies.password, function(err, accessObj)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//there is access, continue //there is access, continue
if(accessObj.accessStatus == "grant") if(accessObj.accessStatus == "grant")
{ {
@ -163,7 +167,7 @@ async.waterfall([
return; return;
} }
} }
res.header('WWW-Authenticate', 'Basic realm="Protected Area"'); res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
if (req.headers.authorization) { if (req.headers.authorization) {
setTimeout(function () { setTimeout(function () {
@ -173,16 +177,16 @@ async.waterfall([
res.send('Authentication required', 401); res.send('Authentication required', 401);
} }
} }
//serve read only pad //serve read only pad
app.get('/ro/:id', function(req, res) app.get('/ro/:id', function(req, res)
{ {
res.header("Server", serverName); res.header("Server", serverName);
var html; var html;
var padId; var padId;
var pad; var pad;
async.series([ async.series([
//translate the read only pad to a padId //translate the read only pad to a padId
function(callback) function(callback)
@ -190,12 +194,12 @@ async.waterfall([
readOnlyManager.getPadId(req.params.id, function(err, _padId) readOnlyManager.getPadId(req.params.id, function(err, _padId)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
padId = _padId; padId = _padId;
//we need that to tell hasPadAcess about the pad //we need that to tell hasPadAcess about the pad
req.params.pad = padId; req.params.pad = padId;
callback(); callback();
}); });
}, },
@ -203,12 +207,12 @@ async.waterfall([
function(callback) function(callback)
{ {
//return if the there is no padId //return if the there is no padId
if(padId == null) if(!padId)
{ {
callback("notfound"); callback("notfound");
return; return;
} }
hasPadAccess(req, res, function() hasPadAccess(req, res, function()
{ {
//render the html document //render the html document
@ -225,29 +229,29 @@ async.waterfall([
//throw any unexpected error //throw any unexpected error
if(err && err != "notfound") if(err && err != "notfound")
ERR(err); ERR(err);
if(err == "notfound") if(err == "notfound")
res.send('404 - Not Found', 404); res.send('404 - Not Found', 404);
else else
res.send(html); res.send(html);
}); });
}); });
//serve pad.html under /p //serve pad.html under /p
app.get('/p/:pad', function(req, res, next) app.get('/p/:pad', function(req, res, next)
{ {
//ensure the padname is valid and the url doesn't end with a / //ensure the padname is valid and the url doesn't end with a /
if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url)) if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url))
{ {
res.send('Such a padname is forbidden', 404); res.send('Such a padname is forbidden', 404);
return; return;
} }
res.header("Server", serverName); res.header("Server", serverName);
var filePath = path.normalize(__dirname + "/../static/pad.html"); var filePath = path.normalize(__dirname + "/../static/pad.html");
res.sendfile(filePath, { maxAge: exports.maxAge }); res.sendfile(filePath, { maxAge: exports.maxAge });
}); });
//serve timeslider.html under /p/$padname/timeslider //serve timeslider.html under /p/$padname/timeslider
app.get('/p/:pad/timeslider', function(req, res, next) app.get('/p/:pad/timeslider', function(req, res, next)
{ {
@ -257,12 +261,12 @@ async.waterfall([
res.send('Such a padname is forbidden', 404); res.send('Such a padname is forbidden', 404);
return; return;
} }
res.header("Server", serverName); res.header("Server", serverName);
var filePath = path.normalize(__dirname + "/../static/timeslider.html"); var filePath = path.normalize(__dirname + "/../static/timeslider.html");
res.sendfile(filePath, { maxAge: exports.maxAge }); res.sendfile(filePath, { maxAge: exports.maxAge });
}); });
//serve timeslider.html under /p/$padname/timeslider //serve timeslider.html under /p/$padname/timeslider
app.get('/p/:pad/export/:type', function(req, res, next) app.get('/p/:pad/export/:type', function(req, res, next)
{ {
@ -272,7 +276,7 @@ async.waterfall([
res.send('Such a padname is forbidden', 404); res.send('Such a padname is forbidden', 404);
return; return;
} }
var types = ["pdf", "doc", "txt", "html", "odt", "dokuwiki"]; var types = ["pdf", "doc", "txt", "html", "odt", "dokuwiki"];
//send a 404 if we don't support this filetype //send a 404 if we don't support this filetype
if(types.indexOf(req.params.type) == -1) if(types.indexOf(req.params.type) == -1)
@ -280,24 +284,23 @@ async.waterfall([
next(); next();
return; return;
} }
//if abiword is disabled, and this is a format we only support with abiword, output a message //if abiword is disabled, and this is a format we only support with abiword, output a message
if(settings.abiword == null && if(!settings.abiword && ["odt", "pdf", "doc"].indexOf(req.params.type) !== -1)
["odt", "pdf", "doc"].indexOf(req.params.type) !== -1)
{ {
res.send("Abiword is not enabled at this Etherpad Lite instance. Set the path to Abiword in settings.json to enable this feature"); res.send("Abiword is not enabled at this Etherpad Lite instance. Set the path to Abiword in settings.json to enable this feature");
return; return;
} }
res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Origin", "*");
res.header("Server", serverName); res.header("Server", serverName);
hasPadAccess(req, res, function() hasPadAccess(req, res, function()
{ {
exportHandler.doExport(req, res, req.params.pad, req.params.type); exportHandler.doExport(req, res, req.params.pad, req.params.type);
}); });
}); });
//handle import requests //handle import requests
app.post('/p/:pad/import', function(req, res, next) app.post('/p/:pad/import', function(req, res, next)
{ {
@ -307,84 +310,84 @@ async.waterfall([
res.send('Such a padname is forbidden', 404); res.send('Such a padname is forbidden', 404);
return; return;
} }
//if abiword is disabled, skip handling this request //if abiword is disabled, skip handling this request
if(settings.abiword == null) if(!settings.abiword)
{ {
next(); next();
return; return;
} }
res.header("Server", serverName); res.header("Server", serverName);
hasPadAccess(req, res, function() hasPadAccess(req, res, function()
{ {
importHandler.doImport(req, res, req.params.pad); importHandler.doImport(req, res, req.params.pad);
}); });
}); });
var apiLogger = log4js.getLogger("API"); var apiLogger = log4js.getLogger("API");
//This is for making an api call, collecting all post information and passing it to the apiHandler //This is for making an api call, collecting all post information and passing it to the apiHandler
var apiCaller = function(req, res, fields) { var apiCaller = function(req, res, fields) {
res.header("Server", serverName); res.header("Server", serverName);
res.header("Content-Type", "application/json; charset=utf-8"); res.header("Content-Type", "application/json; charset=utf-8");
apiLogger.info("REQUEST, " + req.params.func + ", " + JSON.stringify(fields)); apiLogger.info("REQUEST, " + req.params.func + ", " + JSON.stringify(fields));
//wrap the send function so we can log the response //wrap the send function so we can log the response
res._send = res.send; res._send = res.send;
res.send = function(response) res.send = function(response)
{ {
response = JSON.stringify(response); response = JSON.stringify(response);
apiLogger.info("RESPONSE, " + req.params.func + ", " + response); apiLogger.info("RESPONSE, " + req.params.func + ", " + response);
//is this a jsonp call, if yes, add the function call //is this a jsonp call, if yes, add the function call
if(req.query.jsonp) if(req.query.jsonp)
response = req.query.jsonp + "(" + response + ")"; response = req.query.jsonp + "(" + response + ")";
res._send(response); res._send(response);
} };
//call the api handler //call the api handler
apiHandler.handle(req.params.func, fields, req, res); apiHandler.handle(req.params.func, fields, req, res);
} };
//This is a api GET call, collect all post informations and pass it to the apiHandler //This is a api GET call, collect all post informations and pass it to the apiHandler
app.get('/api/1/:func', function(req, res) app.get('/api/1/:func', function(req, res)
{ {
apiCaller(req, res, req.query) apiCaller(req, res, req.query);
}); });
//This is a api POST call, collect all post informations and pass it to the apiHandler //This is a api POST call, collect all post informations and pass it to the apiHandler
app.post('/api/1/:func', function(req, res) app.post('/api/1/:func', function(req, res)
{ {
new formidable.IncomingForm().parse(req, function(err, fields, files) new formidable.IncomingForm().parse(req, function(err, fields, files)
{ {
apiCaller(req, res, fields) apiCaller(req, res, fields);
}); });
}); });
//The Etherpad client side sends information about how a disconnect happen //The Etherpad client side sends information about how a disconnect happen
app.post('/ep/pad/connection-diagnostic-info', function(req, res) app.post('/ep/pad/connection-diagnostic-info', function(req, res)
{ {
new formidable.IncomingForm().parse(req, function(err, fields, files) new formidable.IncomingForm().parse(req, function(err, fields, files)
{ {
console.log("DIAGNOSTIC-INFO: " + fields.diagnosticInfo); console.log("DIAGNOSTIC-INFO: " + fields.diagnosticInfo);
res.end("OK"); res.end("OK");
}); });
}); });
//The Etherpad client side sends information about client side javscript errors //The Etherpad client side sends information about client side javscript errors
app.post('/jserror', function(req, res) app.post('/jserror', function(req, res)
{ {
new formidable.IncomingForm().parse(req, function(err, fields, files) new formidable.IncomingForm().parse(req, function(err, fields, files)
{ {
console.error("CLIENT SIDE JAVASCRIPT ERROR: " + fields.errorInfo); console.error("CLIENT SIDE JAVASCRIPT ERROR: " + fields.errorInfo);
res.end("OK"); res.end("OK");
}); });
}); });
//serve index.html under / //serve index.html under /
app.get('/', function(req, res) app.get('/', function(req, res)
{ {
@ -392,7 +395,7 @@ async.waterfall([
var filePath = path.normalize(__dirname + "/../static/index.html"); var filePath = path.normalize(__dirname + "/../static/index.html");
res.sendfile(filePath, { maxAge: exports.maxAge }); res.sendfile(filePath, { maxAge: exports.maxAge });
}); });
//serve robots.txt //serve robots.txt
app.get('/robots.txt', function(req, res) app.get('/robots.txt', function(req, res)
{ {
@ -400,7 +403,7 @@ async.waterfall([
var filePath = path.normalize(__dirname + "/../static/robots.txt"); var filePath = path.normalize(__dirname + "/../static/robots.txt");
res.sendfile(filePath, { maxAge: exports.maxAge }); res.sendfile(filePath, { maxAge: exports.maxAge });
}); });
//serve favicon.ico //serve favicon.ico
app.get('/favicon.ico', function(req, res) app.get('/favicon.ico', function(req, res)
{ {
@ -416,7 +419,7 @@ async.waterfall([
} }
}); });
}); });
//let the server listen //let the server listen
app.listen(settings.port, settings.ip); app.listen(settings.port, settings.ip);
console.log("Server is listening at " + settings.ip + ":" + settings.port); console.log("Server is listening at " + settings.ip + ":" + settings.port);
@ -432,13 +435,13 @@ async.waterfall([
{ {
console.error(err); console.error(err);
} }
//ensure there is only one graceful shutdown running //ensure there is only one graceful shutdown running
if(onShutdown) return; if(onShutdown) return;
onShutdown = true; onShutdown = true;
console.log("graceful shutdown..."); console.log("graceful shutdown...");
//stop the http server //stop the http server
app.close(); app.close();
@ -446,14 +449,14 @@ async.waterfall([
db.db.doShutdown(function() db.db.doShutdown(function()
{ {
console.log("db sucessfully closed."); console.log("db sucessfully closed.");
process.exit(0); process.exit(0);
}); });
setTimeout(function(){ setTimeout(function(){
process.exit(1); process.exit(1);
}, 3000); }, 3000);
} };
//connect graceful shutdown with sigint and uncaughtexception //connect graceful shutdown with sigint and uncaughtexception
if(os.type().indexOf("Windows") == -1) if(os.type().indexOf("Windows") == -1)
@ -462,22 +465,22 @@ async.waterfall([
//https://github.com/joyent/node/issues/1553 //https://github.com/joyent/node/issues/1553
process.on('SIGINT', gracefulShutdown); process.on('SIGINT', gracefulShutdown);
} }
process.on('uncaughtException', gracefulShutdown); process.on('uncaughtException', gracefulShutdown);
//init socket.io and redirect all requests to the MessageHandler //init socket.io and redirect all requests to the MessageHandler
var io = socketio.listen(app); var io = socketio.listen(app);
//this is only a workaround to ensure it works with all browers behind a proxy //this is only a workaround to ensure it works with all browers behind a proxy
//we should remove this when the new socket.io version is more stable //we should remove this when the new socket.io version is more stable
io.set('transports', ['xhr-polling']); io.set('transports', ['xhr-polling']);
var socketIOLogger = log4js.getLogger("socket.io"); var socketIOLogger = log4js.getLogger("socket.io");
io.set('logger', { io.set('logger', {
debug: function (str) debug: function (str)
{ {
socketIOLogger.debug.apply(socketIOLogger, arguments); socketIOLogger.debug.apply(socketIOLogger, arguments);
}, },
info: function (str) info: function (str)
{ {
socketIOLogger.info.apply(socketIOLogger, arguments); socketIOLogger.info.apply(socketIOLogger, arguments);
@ -489,21 +492,21 @@ async.waterfall([
error: function (str) error: function (str)
{ {
socketIOLogger.error.apply(socketIOLogger, arguments); socketIOLogger.error.apply(socketIOLogger, arguments);
}, }
}); });
//minify socket.io javascript //minify socket.io javascript
if(settings.minify) if(settings.minify)
io.enable('browser client minification'); io.enable('browser client minification');
var padMessageHandler = require("./handler/PadMessageHandler"); var padMessageHandler = require("./handler/PadMessageHandler");
var timesliderMessageHandler = require("./handler/TimesliderMessageHandler"); var timesliderMessageHandler = require("./handler/TimesliderMessageHandler");
//Initalize the Socket.IO Router //Initalize the Socket.IO Router
socketIORouter.setSocketIO(io); socketIORouter.setSocketIO(io);
socketIORouter.addComponent("pad", padMessageHandler); socketIORouter.addComponent("pad", padMessageHandler);
socketIORouter.addComponent("timeslider", timesliderMessageHandler); socketIORouter.addComponent("timeslider", timesliderMessageHandler);
callback(null); callback(null);
} }
]); ]);

View file

@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
var util = require('util'); var util = require('util');
var spawn = require('child_process').spawn; var spawn = require('child_process').spawn;
var async = require("async"); var async = require("async");
@ -35,7 +35,7 @@ if(os.type().indexOf("Windows") > -1)
{ {
//span an abiword process to perform the conversion //span an abiword process to perform the conversion
var abiword = spawn(settings.abiword, ["--to=" + task.destFile, task.srcFile]); var abiword = spawn(settings.abiword, ["--to=" + task.destFile, task.srcFile]);
//delegate the processing of stdout to another function //delegate the processing of stdout to another function
abiword.stdout.on('data', function (data) abiword.stdout.on('data', function (data)
{ {
@ -44,7 +44,7 @@ if(os.type().indexOf("Windows") > -1)
}); });
//append error messages to the buffer //append error messages to the buffer
abiword.stderr.on('data', function (data) abiword.stderr.on('data', function (data)
{ {
stdoutBuffer += data.toString(); stdoutBuffer += data.toString();
}); });
@ -52,19 +52,19 @@ if(os.type().indexOf("Windows") > -1)
//throw exceptions if abiword is dieing //throw exceptions if abiword is dieing
abiword.on('exit', function (code) abiword.on('exit', function (code)
{ {
if(code != 0) { if(code !== 0) {
throw "Abiword died with exit code " + code; throw "Abiword died with exit code " + code;
} }
if(stdoutBuffer != "") if(stdoutBuffer !== "")
{ {
console.log(stdoutBuffer); console.log(stdoutBuffer);
} }
callback(); callback();
}); });
} };
exports.convertFile = function(srcFile, destFile, type, callback) exports.convertFile = function(srcFile, destFile, type, callback)
{ {
doConvertTask({"srcFile": srcFile, "destFile": destFile, "type": type}, callback); doConvertTask({"srcFile": srcFile, "destFile": destFile, "type": type}, callback);
@ -78,67 +78,68 @@ else
var abiword = spawn(settings.abiword, ["--plugin", "AbiCommand"]); var abiword = spawn(settings.abiword, ["--plugin", "AbiCommand"]);
//append error messages to the buffer //append error messages to the buffer
abiword.stderr.on('data', function (data) abiword.stderr.on('data', function (data)
{ {
stdoutBuffer += data.toString(); stdoutBuffer += data.toString();
}); });
//throw exceptions if abiword is dieing //throw exceptions if abiword is dieing
abiword.on('exit', function (code) abiword.on('exit', function (code)
{ {
throw "Abiword died with exit code " + 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 stdoutCallback = null;
var stdoutBuffer = ""; var stdoutBuffer = "";
var firstPrompt = true; var firstPrompt = true;
function onAbiwordStdout(data) var onAbiwordStdout = function onAbiwordStdout(data)
{ {
//add data to buffer //add data to buffer
stdoutBuffer+=data.toString(); stdoutBuffer+=data.toString();
//we're searching for the prompt, cause this means everything we need is in the buffer //we're searching for the prompt, cause this means everything we need is in the buffer
if(stdoutBuffer.search("AbiWord:>") != -1) if(stdoutBuffer.search("AbiWord:>") != -1)
{ {
//filter the feedback message //filter the feedback message
var err = stdoutBuffer.search("OK") != -1 ? null : stdoutBuffer; var err = stdoutBuffer.search("OK") != -1 ? null : stdoutBuffer;
//reset the buffer //reset the buffer
stdoutBuffer = ""; stdoutBuffer = "";
//call the callback with the error message //call the callback with the error message
//skip the first prompt //skip the first prompt
if(stdoutCallback != null && !firstPrompt) if(stdoutCallback && !firstPrompt)
{ {
stdoutCallback(err); stdoutCallback(err);
stdoutCallback = null; stdoutCallback = null;
} }
firstPrompt = false; firstPrompt = false;
} }
} };
//delegate the processing of stdout to a other function
abiword.stdout.on('data',onAbiwordStdout);
doConvertTask = function(task, callback) doConvertTask = function(task, callback)
{ {
abiword.stdin.write("convert " + task.srcFile + " " + task.destFile + " " + task.type + "\n"); abiword.stdin.write("convert " + task.srcFile + " " + task.destFile + " " + task.type + "\n");
//create a callback that calls the task callback and the caller callback //create a callback that calls the task callback and the caller callback
stdoutCallback = function (err) stdoutCallback = function (err)
{ {
callback(); callback();
task.callback(err); task.callback(err);
}; };
} };
//Queue with the converts we have to do //Queue with the converts we have to do
var queue = async.queue(doConvertTask, 1); var queue = async.queue(doConvertTask, 1);
exports.convertFile = function(srcFile, destFile, type, callback) exports.convertFile = function(srcFile, destFile, type, callback)
{ {
queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback}); queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback});
}; };
} }

View file

@ -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 * 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 * 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 * https://github.com/Pita/etherpad-lite/blob/master/doc/easysync/easysync-notes.txt
*/ */
@ -87,4 +87,4 @@ exports.createAttributePool = function () {
}; };
return p; return p;
} };

View file

@ -1,10 +1,10 @@
/* /*
* This is the Changeset library copied from the old Etherpad with some modifications to use it in node.js * 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 * 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. * This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/ */
@ -66,7 +66,7 @@ exports.newLen = function (cs) {
exports.opIterator = function (opsStr, optStartIndex) { exports.opIterator = function (opsStr, optStartIndex) {
//print(opsStr); //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 startIndex = (optStartIndex || 0);
var curIndex = startIndex; var curIndex = startIndex;
var prevIndex = curIndex; var prevIndex = curIndex;
@ -195,12 +195,10 @@ exports.checkRep = function (cs) {
exports.assert(oldPos < oldLen, oldPos, " >= ", oldLen, " in ", cs); exports.assert(oldPos < oldLen, oldPos, " >= ", oldLen, " in ", cs);
break; break;
case '+': case '+':
{ calcNewLen += o.chars;
calcNewLen += o.chars; numInserted += o.chars;
numInserted += o.chars; exports.assert(calcNewLen < newLen, calcNewLen, " >= ", newLen, " in ", cs);
exports.assert(calcNewLen < newLen, calcNewLen, " >= ", newLen, " in ", cs); break;
break;
}
} }
assem.append(o); assem.append(o);
} }
@ -216,7 +214,7 @@ exports.checkRep = function (cs) {
exports.assert(normalized == cs, normalized, ' != ', cs); exports.assert(normalized == cs, normalized, ' != ', cs);
return cs; return cs;
} };
exports.smartOpAssembler = function () { exports.smartOpAssembler = function () {
// Like opAssembler but able to produce conforming exportss // Like opAssembler but able to produce conforming exportss
@ -387,7 +385,7 @@ if (_opt) {
bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars; bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars;
bufOp.lines += op.lines; bufOp.lines += op.lines;
bufOpAdditionalCharsAfterNewline = 0; bufOpAdditionalCharsAfterNewline = 0;
} else if (bufOp.lines == 0) { } else if (bufOp.lines === 0) {
// both bufOp and op are in-line // both bufOp and op are in-line
bufOp.chars += op.chars; bufOp.chars += op.chars;
} else { } else {
@ -635,9 +633,9 @@ exports.textLinesMutator = function (lines) {
} }
//print(inSplice+" / "+isCurLineInSplice()+" / "+curSplice[0]+" / "+curSplice[1]+" / "+lines.length); //print(inSplice+" / "+isCurLineInSplice()+" / "+curSplice[0]+" / "+curSplice[1]+" / "+lines.length);
/*if (inSplice && (! isCurLineInSplice()) && (curSplice[0] + curSplice[1] < lines.length)) { /*if (inSplice && (! isCurLineInSplice()) && (curSplice[0] + curSplice[1] < lines.length)) {
print("BLAH"); print("BLAH");
putCurLineInSplice(); putCurLineInSplice();
}*/ }*/
// tests case foo in remove(), which isn't otherwise covered in current impl // tests case foo in remove(), which isn't otherwise covered in current impl
} }
//debugPrint("skip"); //debugPrint("skip");
@ -667,13 +665,13 @@ exports.textLinesMutator = function (lines) {
enterSplice(); enterSplice();
} }
function nextKLinesText(k) { var nextKLinesText = function nextKLinesText(k) {
var m = curSplice[0] + curSplice[1]; var m = curSplice[0] + curSplice[1];
return lines_slice(m, m + k).join(''); return lines_slice(m, m + k).join('');
} };
if (isCurLineInSplice()) { if (isCurLineInSplice()) {
//print(curCol); //print(curCol);
if (curCol == 0) { if (curCol === 0) {
removed = curSplice[curSplice.length - 1]; removed = curSplice[curSplice.length - 1];
// print("FOO"); // case foo // print("FOO"); // case foo
curSplice.length--; curSplice.length--;
@ -719,6 +717,7 @@ exports.textLinesMutator = function (lines) {
if (!inSplice) { if (!inSplice) {
enterSplice(); enterSplice();
} }
var sline;
if (L) { if (L) {
var newLines = exports.splitTextLines(text); var newLines = exports.splitTextLines(text);
if (isCurLineInSplice()) { if (isCurLineInSplice()) {
@ -729,7 +728,7 @@ exports.textLinesMutator = function (lines) {
//curLine += newLines.length; //curLine += newLines.length;
//} //}
//else { //else {
var sline = curSplice.length - 1; sline = curSplice.length - 1;
var theLine = curSplice[sline]; var theLine = curSplice[sline];
var lineCol = curCol; var lineCol = curCol;
curSplice[sline] = theLine.substring(0, lineCol) + newLines[0]; curSplice[sline] = theLine.substring(0, lineCol) + newLines[0];
@ -745,7 +744,7 @@ exports.textLinesMutator = function (lines) {
curLine += newLines.length; curLine += newLines.length;
} }
} else { } else {
var sline = putCurLineInSplice(); sline = putCurLineInSplice();
curSplice[sline] = curSplice[sline].substring(0, curCol) + text + curSplice[sline].substring(curCol); curSplice[sline] = curSplice[sline].substring(0, curCol) + text + curSplice[sline].substring(curCol);
curCol += text.length; curCol += text.length;
} }
@ -949,74 +948,66 @@ exports._slicerZipperFunc = function (attOp, csOp, opOut, pool) {
} else { } else {
switch (csOp.opcode) { switch (csOp.opcode) {
case '-': case '-':
{ if (csOp.chars <= attOp.chars) {
if (csOp.chars <= attOp.chars) { // delete or delete part
// delete or delete part if (attOp.opcode == '=') {
if (attOp.opcode == '=') { opOut.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;
opOut.chars = csOp.chars; opOut.chars = csOp.chars;
opOut.lines = csOp.lines; opOut.lines = csOp.lines;
opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool); opOut.attribs = '';
csOp.opcode = ''; }
attOp.chars -= csOp.chars; attOp.chars -= csOp.chars;
attOp.lines -= csOp.lines; attOp.lines -= csOp.lines;
if (!attOp.chars) { csOp.opcode = '';
attOp.opcode = ''; if (!attOp.chars) {
} attOp.opcode = '';
} else { }
// keep and keep going } else {
opOut.opcode = attOp.opcode; // delete and keep going
if (attOp.opcode == '=') {
opOut.opcode = '-';
opOut.chars = attOp.chars; opOut.chars = attOp.chars;
opOut.lines = attOp.lines; opOut.lines = attOp.lines;
opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool); opOut.attribs = '';
attOp.opcode = '';
csOp.chars -= attOp.chars;
csOp.lines -= attOp.lines;
} }
break; csOp.chars -= attOp.chars;
} csOp.lines -= attOp.lines;
case '':
{
exports.copyOp(attOp, opOut);
attOp.opcode = ''; 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) { exports.prepareForWire = function (cs, pool) {
var newPool = AttributePoolFactory.createAttributePool();; var newPool = AttributePoolFactory.createAttributePool();
var newCs = exports.moveOpsToNewPool(cs, pool, newPool); var newCs = exports.moveOpsToNewPool(cs, pool, newPool);
return { return {
translated: newCs, translated: newCs,
@ -1480,7 +1471,7 @@ exports.prepareForWire = function (cs, pool) {
exports.isIdentity = function (cs) { exports.isIdentity = function (cs) {
var unpacked = exports.unpack(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) { exports.opAttributeValue = function (op, key, pool) {

View file

@ -1,12 +1,12 @@
/** /**
* Copyright 2011 Adrian Lang * Copyright 2011 Adrian Lang
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS, * distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -28,7 +28,7 @@ function getPadDokuWiki(pad, revNum, callback)
function (callback) function (callback)
{ {
if (revNum != undefined) if (revNum)
{ {
pad.getInternalRevisionAText(revNum, function (err, revisionAtext) pad.getInternalRevisionAText(revNum, function (err, revisionAtext)
{ {
@ -122,6 +122,8 @@ function getDokuWikiFromAtext(pad, atext)
var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars)); var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
idx += numChars; idx += numChars;
var i;
while (iter.hasNext()) while (iter.hasNext())
{ {
var o = iter.next(); 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) if (propVals[i] === true)
{ {
@ -160,7 +162,7 @@ function getDokuWikiFromAtext(pad, atext)
{ {
// leaving bold (e.g.) also leaves italics, etc. // leaving bold (e.g.) also leaves italics, etc.
var left = false; var left = false;
for (var i = 0; i < propVals.length; i++) for (i = 0; i < propVals.length; i++)
{ {
var v = propVals[i]; var v = propVals[i];
if (!left) 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) if (propVals[i] === LEAVE)
{ {
@ -191,7 +193,7 @@ function getDokuWikiFromAtext(pad, atext)
emitCloseTag(i); emitCloseTag(i);
} }
} }
for (var i = 0; i < propVals.length; i++) for (i = 0; i < propVals.length; i++)
{ {
if (propVals[i] === ENTER || propVals[i] === STAY) if (propVals[i] === ENTER || propVals[i] === STAY)
{ {
@ -210,7 +212,7 @@ function getDokuWikiFromAtext(pad, atext)
assem.append(_escapeDokuWiki(s)); assem.append(_escapeDokuWiki(s));
} // end iteration over spans in line } // 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]) if (propVals[i])
{ {
@ -310,7 +312,7 @@ exports.getPadDokuWikiDocument = function (padId, revNum, callback)
getPadDokuWiki(pad, revNum, callback); getPadDokuWiki(pad, revNum, callback);
}); });
} };
function _escapeDokuWiki(s) function _escapeDokuWiki(s)
{ {
@ -321,7 +323,7 @@ function _escapeDokuWiki(s)
// copied from ACE // 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_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_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'); 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], ...] // returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]

View file

@ -1,12 +1,12 @@
/** /**
* Copyright 2009 Google Inc. * Copyright 2009 Google Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS, * distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -55,7 +55,7 @@ function getPadHTML(pad, revNum, callback)
function (callback) function (callback)
{ {
if (revNum != undefined) if (revNum)
{ {
pad.getInternalRevisionAText(revNum, function (err, revisionAtext) pad.getInternalRevisionAText(revNum, function (err, revisionAtext)
{ {
@ -140,7 +140,7 @@ function getHTMLFromAtext(pad, atext)
assem.append(tags[i]); assem.append(tags[i]);
assem.append('>'); assem.append('>');
} }
function orderdCloseTags(tags2close) function orderdCloseTags(tags2close)
{ {
for(var i=0;i<openTags.length;i++) for(var i=0;i<openTags.length;i++)
@ -171,6 +171,8 @@ function getHTMLFromAtext(pad, atext)
var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars)); var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
idx += numChars; idx += numChars;
var tags2close;
while (iter.hasNext()) while (iter.hasNext())
{ {
var o = iter.next(); var o = iter.next();
@ -191,7 +193,9 @@ function getHTMLFromAtext(pad, atext)
} }
} }
}); });
for (var i = 0; i < propVals.length; i++)
var i;
for (i = 0; i < propVals.length; i++)
{ {
if (propVals[i] === true) if (propVals[i] === true)
{ {
@ -209,7 +213,7 @@ function getHTMLFromAtext(pad, atext)
{ {
// leaving bold (e.g.) also leaves italics, etc. // leaving bold (e.g.) also leaves italics, etc.
var left = false; var left = false;
for (var i = 0; i < propVals.length; i++) for (i = 0; i < propVals.length; i++)
{ {
var v = propVals[i]; var v = propVals[i];
if (!left) if (!left)
@ -228,9 +232,9 @@ function getHTMLFromAtext(pad, atext)
} }
} }
var tags2close = []; tags2close = [];
for (var i = propVals.length - 1; i >= 0; i--) for (i = propVals.length - 1; i >= 0; i--)
{ {
if (propVals[i] === LEAVE) if (propVals[i] === LEAVE)
{ {
@ -244,10 +248,10 @@ function getHTMLFromAtext(pad, atext)
tags2close.push(i); tags2close.push(i);
} }
} }
orderdCloseTags(tags2close); orderdCloseTags(tags2close);
for (var i = 0; i < propVals.length; i++) for (i = 0; i < propVals.length; i++)
{ {
if (propVals[i] === ENTER || propVals[i] === STAY) if (propVals[i] === ENTER || propVals[i] === STAY)
{ {
@ -262,26 +266,26 @@ function getHTMLFromAtext(pad, atext)
{ {
chars--; // exclude newline at end of line, if present chars--; // exclude newline at end of line, if present
} }
var s = taker.take(chars); 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 //from but they break the abiword parser and are completly useless
s = s.replace(String.fromCharCode(12), ""); s = s.replace(String.fromCharCode(12), "");
assem.append(_escapeHTML(s)); assem.append(_escapeHTML(s));
} // end iteration over spans in line } // end iteration over spans in line
var tags2close = []; tags2close = [];
for (var i = propVals.length - 1; i >= 0; i--) for (var x = propVals.length - 1; x >= 0; x--)
{ {
if (propVals[i]) if (propVals[x])
{ {
tags2close.push(i); tags2close.push(x);
propVals[i] = false; propVals[x] = false;
} }
} }
orderdCloseTags(tags2close); orderdCloseTags(tags2close);
} // end processNextChars } // end processNextChars
if (urls) if (urls)
@ -425,7 +429,7 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
callback(null, head + html + foot); callback(null, head + html + foot);
}); });
}); });
} };
function _escapeHTML(s) function _escapeHTML(s)
{ {
@ -436,18 +440,18 @@ function _escapeHTML(s)
re.MAP = { re.MAP = {
'&': '&amp;', '&': '&amp;',
'<': '&lt;', '<': '&lt;',
'>': '&gt;', '>': '&gt;'
}; };
} }
s = s.replace(re, function (c) s = s.replace(re, function (c)
{ {
return re.MAP[c]; return re.MAP[c];
}); });
return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(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); parts.push(m);
}); });
var i;
var p;
if (doesWrap) if (doesWrap)
{ {
var endOfLine = true; var endOfLine = true;
var beforeSpace = false; var beforeSpace = false;
// last space in a run is normal, others are nbsp, // last space in a run is normal, others are nbsp,
// end of line is 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 (p == " ")
{ {
if (endOfLine || beforeSpace) parts[i] = '&nbsp;'; if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
@ -489,9 +497,9 @@ function _processSpaces(s)
} }
} }
// beginning of line is nbsp // 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 == " ") if (p == " ")
{ {
parts[i] = '&nbsp;'; parts[i] = '&nbsp;';
@ -505,9 +513,9 @@ function _processSpaces(s)
} }
else 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 == " ") if (p == " ")
{ {
parts[i] = '&nbsp;'; parts[i] = '&nbsp;';
@ -521,7 +529,7 @@ function _processSpaces(s)
// copied from ACE // 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_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_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'); 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], ...] // returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]

View file

@ -1,7 +1,7 @@
/** /**
* This Module manages all /minified/* requests. It controls the * This Module manages all /minified/\* requests. It controls the
* minification && compression of Javascript and CSS. * minification && compression of Javascript and CSS.
*/ */
/* /*
* 2011 Peter 'Pita' Martischka (Primary Technology Ltd) * 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
@ -28,7 +28,7 @@ var jsp = require("uglify-js").parser;
var pro = require("uglify-js").uglify; var pro = require("uglify-js").uglify;
var path = require('path'); var path = require('path');
var Buffer = require('buffer').Buffer; var Buffer = require('buffer').Buffer;
var gzip = require('gzip'); var zlib = require('zlib');
var server = require('../server'); var server = require('../server');
var os = require('os'); 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) exports.minifyJS = function(req, res, jsFilename)
{ {
res.header("Content-Type","text/javascript"); res.header("Content-Type","text/javascript");
//choose the js files we need //choose the js files we need
if(jsFilename == "pad.js") if(jsFilename == "pad.js")
{ {
@ -58,20 +58,22 @@ exports.minifyJS = function(req, res, jsFilename)
{ {
throw new Error("there is no profile for creating " + name); throw new Error("there is no profile for creating " + name);
} }
var fileValues;
//minifying is enabled //minifying is enabled
if(settings.minify) if(settings.minify)
{ {
var fileValues = {}; fileValues = {};
var embeds = {}; var embeds = {};
var latestModification = 0; var latestModification = 0;
async.series([ async.series([
//find out the highest modification date //find out the highest modification date
function(callback) function(callback)
{ {
var folders2check = ["../static/css","../static/js"]; var folders2check = ["../static/css","../static/js"];
//go trough this two folders //go trough this two folders
async.forEach(folders2check, function(path, callback) async.forEach(folders2check, function(path, callback)
{ {
@ -79,27 +81,27 @@ exports.minifyJS = function(req, res, jsFilename)
fs.readdir(path, function(err, files) fs.readdir(path, function(err, files)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//we wanna check the directory itself for changes too //we wanna check the directory itself for changes too
files.push("."); files.push(".");
//go trough all files in this folder //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 //get the stat data of this file
fs.stat(path + "/" + filename, function(err, stats) fs.stat(path + "/" + filename, function(err, stats)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//get the modification time //get the modification time
var modificationTime = stats.mtime.getTime(); var modificationTime = stats.mtime.getTime();
//compare the modification time to the highest found //compare the modification time to the highest found
if(modificationTime > latestModification) if(modificationTime > latestModification)
{ {
latestModification = modificationTime; latestModification = modificationTime;
} }
callback(); callback();
}); });
}, callback); }, callback);
@ -116,7 +118,7 @@ exports.minifyJS = function(req, res, jsFilename)
ERR(err, callback); ERR(err, callback);
return; return;
} }
//there is no minfied file or there new changes since this file was generated, so continue generating this file //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) if((err && err.code == "ENOENT") || stats.mtime.getTime() < latestModification)
{ {
@ -128,48 +130,48 @@ exports.minifyJS = function(req, res, jsFilename)
callback("stop"); callback("stop");
} }
}); });
}, },
//load all js files //load all js files
function (callback) function (callback)
{ {
async.forEach(jsFiles, function (item, callback) async.forEach(jsFiles, function (item, callback)
{ {
fs.readFile("../static/js/" + item, "utf-8", function(err, data) fs.readFile("../static/js/" + item, "utf-8", function(err, data)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
fileValues[item] = data; fileValues[item] = data;
callback(); callback();
}); });
}, callback); }, callback);
}, },
//find all includes in ace.js and embed them //find all includes in ace.js and embed them
function(callback) function(callback)
{ {
//if this is not the creation of pad.js, skip this part //if this is not the creation of pad.js, skip this part
if(jsFilename != "pad.js") if(jsFilename != "pad.js")
{ {
callback(); callback();
return; return;
} }
var founds = fileValues["ace.js"].match(/\$\$INCLUDE_[a-zA-Z_]+\([a-zA-Z0-9.\/_"]+\)/gi); var founds = fileValues["ace.js"].match(/\$\$INCLUDE_[a-zA-Z_]+\([a-zA-Z0-9.\/_"]+\)/gi);
//go trough all includes //go trough all includes
async.forEach(founds, function (item, callback) async.forEach(founds, function (item, callback)
{ {
var filename = item.match(/"[^"]*"/g)[0].substr(1); var filename = item.match(/"[^"]*"/g)[0].substr(1);
filename = filename.substr(0,filename.length-1); filename = filename.substr(0,filename.length-1);
var type = item.match(/INCLUDE_[A-Z]+/g)[0].substr("INCLUDE_".length); var type = item.match(/INCLUDE_[A-Z]+/g)[0].substr("INCLUDE_".length);
var quote = item.search("_Q") != -1; var quote = item.search("_Q") != -1;
//read the included file //read the included file
fs.readFile(filename, "utf-8", function(err, data) fs.readFile(filename, "utf-8", function(err, data)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//compress the file //compress the file
if(type == "JS") if(type == "JS")
{ {
embeds[item] = "<script>\n" + compressJS([data])+ "\n\\x3c/script>"; embeds[item] = "<script>\n" + compressJS([data])+ "\n\\x3c/script>";
@ -178,36 +180,36 @@ exports.minifyJS = function(req, res, jsFilename)
{ {
embeds[item] = "<style>" + compressCSS([data])+ "</style>"; embeds[item] = "<style>" + compressCSS([data])+ "</style>";
} }
//do the first escape //do the first escape
embeds[item] = JSON.stringify(embeds[item]).replace(/'/g, "\\'").replace(/\\"/g, "\""); embeds[item] = JSON.stringify(embeds[item]).replace(/'/g, "\\'").replace(/\\"/g, "\"");
embeds[item] = embeds[item].substr(1); embeds[item] = embeds[item].substr(1);
embeds[item] = embeds[item].substr(0, embeds[item].length-1); embeds[item] = embeds[item].substr(0, embeds[item].length-1);
//add quotes, if wished //add quotes, if wished
if(quote) if(quote)
{ {
embeds[item] = "'" + embeds[item] + "'"; embeds[item] = "'" + embeds[item] + "'";
} }
//do the second escape //do the second escape
embeds[item] = JSON.stringify(embeds[item]).replace(/'/g, "\\'").replace(/\"/g, "\""); embeds[item] = JSON.stringify(embeds[item]).replace(/'/g, "\\'").replace(/\"/g, "\"");
embeds[item] = embeds[item].substr(1); embeds[item] = embeds[item].substr(1);
embeds[item] = embeds[item].substr(0, embeds[item].length-1); embeds[item] = embeds[item].substr(0, embeds[item].length-1);
embeds[item] = "'" + embeds[item] + "'"; embeds[item] = "'" + embeds[item] + "'";
callback(); callback();
}); });
}, function(err) }, function(err)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//replace the include command with the include //replace the include command with the include
for(var i in embeds) for(var i in embeds)
{ {
fileValues["ace.js"]=fileValues["ace.js"].replace(i, embeds[i]); fileValues["ace.js"]=fileValues["ace.js"].replace(i, embeds[i]);
} }
callback(); callback();
}); });
}, },
@ -220,36 +222,25 @@ exports.minifyJS = function(req, res, jsFilename)
{ {
values.push(fileValues[jsFiles[i]]); values.push(fileValues[jsFiles[i]]);
} }
//minify all javascript files to one //minify all javascript files to one
var result = compressJS(values); var result = compressJS(values);
async.parallel([ async.parallel([
//write the results plain in a file //write the results plain in a file
function(callback) function(callback)
{ {
fs.writeFile("../var/minified_" + jsFilename, result, "utf8", callback); fs.writeFile("../var/minified_" + jsFilename, result, "utf8", callback);
}, },
//write the results compressed in a file //write the results compressed in a file
function(callback) function(callback)
{ {
//spawn a gzip process if we're on a unix system zlib.gzip(result, function(err, compressedResult){
if(os.type().indexOf("Windows") == -1)
{ if(ERR(err, callback)) return;
gzip(result, 9, function(err, compressedResult){
//weird gzip bug that returns 0 instead of null if everything is ok fs.writeFile("../var/minified_" + jsFilename + ".gz", compressedResult, callback);
err = err === 0 ? null : err; });
if(ERR(err, callback)) return;
fs.writeFile("../var/minified_" + jsFilename + ".gz", compressedResult, callback);
});
}
//skip this step on windows
else
{
callback();
}
} }
],callback); ],callback);
} }
@ -259,10 +250,10 @@ exports.minifyJS = function(req, res, jsFilename)
{ {
if(ERR(err)) return; if(ERR(err)) return;
} }
//check if gzip is supported by this browser //check if gzip is supported by this browser
var gzipSupport = req.header('Accept-Encoding', '').indexOf('gzip') != -1; var gzipSupport = req.header('Accept-Encoding', '').indexOf('gzip') != -1;
var pathStr; var pathStr;
if(gzipSupport && os.type().indexOf("Windows") == -1) if(gzipSupport && os.type().indexOf("Windows") == -1)
{ {
@ -273,41 +264,41 @@ exports.minifyJS = function(req, res, jsFilename)
{ {
pathStr = path.normalize(__dirname + "/../../var/minified_" + jsFilename ); pathStr = path.normalize(__dirname + "/../../var/minified_" + jsFilename );
} }
res.sendfile(pathStr, { maxAge: server.maxAge }); res.sendfile(pathStr, { maxAge: server.maxAge });
}) });
} }
//minifying is disabled, so put the files together in one file //minifying is disabled, so put the files together in one file
else else
{ {
var fileValues = {}; fileValues = {};
//read all js files //read all js files
async.forEach(jsFiles, function (item, callback) async.forEach(jsFiles, function (item, callback)
{ {
fs.readFile("../static/js/" + item, "utf-8", function(err, data) fs.readFile("../static/js/" + item, "utf-8", function(err, data)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
fileValues[item] = data; fileValues[item] = data;
callback(); callback();
}); });
}, },
//send all files together //send all files together
function(err) function(err)
{ {
if(ERR(err)) return; if(ERR(err)) return;
for(var i=0;i<jsFiles.length;i++) for(var i=0;i<jsFiles.length;i++)
{ {
var fileName = jsFiles[i]; var fileName = jsFiles[i];
res.write("\n\n\n/*** File: static/js/" + fileName + " ***/\n\n\n"); res.write("\n\n\n/*** File: static/js/" + fileName + " ***/\n\n\n");
res.write(fileValues[fileName]); res.write(fileValues[fileName]);
} }
res.end(); res.end();
}); });
} }
} };
function compressJS(values) function compressJS(values)
{ {

7
node/utils/cleantext.js Normal file
View file

@ -0,0 +1,7 @@
/**
* 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, ' ');
};

View file

@ -173,7 +173,7 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
function _isEmpty(node, state) function _isEmpty(node, state)
{ {
// consider clean blank lines pasted in IE to be empty // consider clean blank lines pasted in IE to be empty
if (dom.nodeNumChildren(node) == 0) return true; if (dom.nodeNumChildren(node) === 0) return true;
if (dom.nodeNumChildren(node) == 1 && getAssoc(node, "shouldBeEmpty") && dom.optNodeInnerHTML(node) == "&nbsp;" && !getAssoc(node, "unpasted")) if (dom.nodeNumChildren(node) == 1 && getAssoc(node, "shouldBeEmpty") && dom.optNodeInnerHTML(node) == "&nbsp;" && !getAssoc(node, "unpasted"))
{ {
if (state) if (state)
@ -191,7 +191,7 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
{ {
var ln = lines.length() - 1; var ln = lines.length() - 1;
var chr = lines.textOfLine(ln).length; var chr = lines.textOfLine(ln).length;
if (chr == 0 && state.listType && state.listType != 'none') if (chr === 0 && state.listType && state.listType != 'none')
{ {
chr += 1; // listMarker chr += 1; // listMarker
} }
@ -218,11 +218,11 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
cc.incrementFlag = function(state, flagName) cc.incrementFlag = function(state, flagName)
{ {
state.flags[flagName] = (state.flags[flagName] || 0) + 1; state.flags[flagName] = (state.flags[flagName] || 0) + 1;
} };
cc.decrementFlag = function(state, flagName) cc.decrementFlag = function(state, flagName)
{ {
state.flags[flagName]--; state.flags[flagName]--;
} };
cc.incrementAttrib = function(state, attribName) cc.incrementAttrib = function(state, attribName)
{ {
if (!state.attribs[attribName]) if (!state.attribs[attribName])
@ -234,12 +234,12 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
state.attribs[attribName]++; state.attribs[attribName]++;
} }
_recalcAttribString(state); _recalcAttribString(state);
} };
cc.decrementAttrib = function(state, attribName) cc.decrementAttrib = function(state, attribName)
{ {
state.attribs[attribName]--; state.attribs[attribName]--;
_recalcAttribString(state); _recalcAttribString(state);
} };
function _enterList(state, listType) function _enterList(state, listType)
{ {
@ -315,14 +315,14 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
{ {
if (state) if (state)
{ {
var atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0; var atBeginningOfLine = lines.textOfLine(lines.length() - 1).length === 0;
if (atBeginningOfLine && state.listType && state.listType != 'none') if (atBeginningOfLine && state.listType && state.listType != 'none')
{ {
_produceListMarker(state); _produceListMarker(state);
} }
} }
lines.startNew(); lines.startNew();
} };
cc.notifySelection = function(sel) cc.notifySelection = function(sel)
{ {
if (sel) if (sel)
@ -358,12 +358,15 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
if (isBlock) _ensureColumnZero(state); if (isBlock) _ensureColumnZero(state);
var startLine = lines.length() - 1; var startLine = lines.length() - 1;
_reachBlockPoint(node, 0, state); _reachBlockPoint(node, 0, state);
var i;
if (dom.isNodeText(node)) if (dom.isNodeText(node))
{ {
var txt = dom.nodeValue(node); var txt = dom.nodeValue(node);
var rest = ''; var rest = '';
var x = 0; // offset into original text var x = 0; // offset into original text
if (txt.length == 0) if (txt.length === 0)
{ {
if (startPoint && node == startPoint.node) if (startPoint && node == startPoint.node)
{ {
@ -404,7 +407,7 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
// removing "\n" from pasted HTML will collapse words together. // removing "\n" from pasted HTML will collapse words together.
txt2 = ""; txt2 = "";
} }
var atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0; var atBeginningOfLine = lines.textOfLine(lines.length() - 1).length === 0;
if (atBeginningOfLine) if (atBeginningOfLine)
{ {
// newlines in the source mustn't become spaces at beginning of line box // newlines in the source mustn't become spaces at beginning of line box
@ -425,6 +428,8 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
} }
else else
{ {
var c;
var tname = (dom.nodeTagName(node) || "").toLowerCase(); var tname = (dom.nodeTagName(node) || "").toLowerCase();
if (tname == "br") if (tname == "br")
{ {
@ -472,9 +477,9 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
{ {
cc.doAttrib(state, "strikethrough"); cc.doAttrib(state, "strikethrough");
} }
var type;
if (tname == "ul") if (tname == "ul")
{ {
var type;
var rr = cls && /(?:^| )list-(bullet[12345678])\b/.exec(cls); var rr = cls && /(?:^| )list-(bullet[12345678])\b/.exec(cls);
type = rr && rr[1] || "bullet" + String(Math.min(_MAX_LIST_LEVEL, (state.listNesting || 0) + 1)); type = rr && rr[1] || "bullet" + String(Math.min(_MAX_LIST_LEVEL, (state.listNesting || 0) + 1));
oldListTypeOrNull = (_enterList(state, type) || 'none'); oldListTypeOrNull = (_enterList(state, type) || 'none');
@ -488,9 +493,9 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
var classes = cls.match(/\S+/g); var classes = cls.match(/\S+/g);
if (classes && classes.length > 0) if (classes && classes.length > 0)
{ {
for (var i = 0; i < classes.length; i++) for (i = 0; i < classes.length; i++)
{ {
var c = classes[i]; c = classes[i];
var a = className2Author(c); var a = className2Author(c);
if (a) if (a)
{ {
@ -503,9 +508,9 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
} }
var nc = dom.nodeNumChildren(node); var nc = dom.nodeNumChildren(node);
for (var i = 0; i < nc; i++) for (i = 0; i < nc; i++)
{ {
var c = dom.nodeChild(node, i); c = dom.nodeChild(node, i);
cc.collectContent(c, state); cc.collectContent(c, state);
} }
@ -523,7 +528,7 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
if (isPre) cc.decrementFlag(state, 'preMode'); if (isPre) cc.decrementFlag(state, 'preMode');
if (state.localAttribs) if (state.localAttribs)
{ {
for (var i = 0; i < state.localAttribs.length; i++) for (i = 0; i < state.localAttribs.length; i++)
{ {
cc.decrementAttrib(state, state.localAttribs[i]); cc.decrementAttrib(state, state.localAttribs[i]);
} }
@ -612,6 +617,30 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
var buffer = 10; // chars allowed over before wrapping var buffer = 10; // chars allowed over before wrapping
var linesWrapped = 0; var linesWrapped = 0;
var numLinesAfter = 0; var numLinesAfter = 0;
var fixLineNumber = function fixLineNumber(lineChar)
{
if (lineChar[0] < 0) return;
var n = lineChar[0];
var c = lineChar[1];
if (n > i)
{
n += (newStrings.length - 1);
}
else if (n == i)
{
var a = 0;
while (c > newStrings[a].length)
{
c -= newStrings[a].length;
a++;
}
n += a;
}
lineChar[0] = n;
lineChar[1] = c;
};
for (var i = lineStrings.length - 1; i >= 0; i--) for (var i = lineStrings.length - 1; i >= 0; i--)
{ {
var oldString = lineStrings[i]; var oldString = lineStrings[i];
@ -620,6 +649,8 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
{ {
var newStrings = []; var newStrings = [];
var newAttribStrings = []; var newAttribStrings = [];
while (oldString.length > lineLimit) while (oldString.length > lineLimit)
{ {
//var semiloc = oldString.lastIndexOf(';', lineLimit-1); //var semiloc = oldString.lastIndexOf(';', lineLimit-1);
@ -636,28 +667,6 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
newAttribStrings.push(oldAttribString); newAttribStrings.push(oldAttribString);
} }
function fixLineNumber(lineChar)
{
if (lineChar[0] < 0) return;
var n = lineChar[0];
var c = lineChar[1];
if (n > i)
{
n += (newStrings.length - 1);
}
else if (n == i)
{
var a = 0;
while (c > newStrings[a].length)
{
c -= newStrings[a].length;
a++;
}
n += a;
}
lineChar[0] = n;
lineChar[1] = c;
}
fixLineNumber(ss); fixLineNumber(ss);
fixLineNumber(se); fixLineNumber(se);
linesWrapped++; linesWrapped++;
@ -684,7 +693,7 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
lines: lineStrings, lines: lineStrings,
lineAttribs: lineAttribs lineAttribs: lineAttribs
}; };
} };
return cc; return cc;
} }

View file

@ -5,7 +5,7 @@
"keywords" : ["etherpad", "realtime", "collaborative", "editor"], "keywords" : ["etherpad", "realtime", "collaborative", "editor"],
"author" : "Peter 'Pita' Martischka <petermartischka@googlemail.com> - Primary Technology Ltd", "author" : "Peter 'Pita' Martischka <petermartischka@googlemail.com> - Primary Technology Ltd",
"contributors": [ "contributors": [
{ "name": "John McLear", { "name": "John McLear",
"name": "Hans Pinckaers", "name": "Hans Pinckaers",
"name": "Robin Buse"} "name": "Robin Buse"}
], ],
@ -13,15 +13,16 @@
"socket.io" : "0.8.7", "socket.io" : "0.8.7",
"ueberDB" : "0.1.3", "ueberDB" : "0.1.3",
"async" : "0.1.15", "async" : "0.1.15",
"joose" : "3.50.0",
"express" : "2.5.0", "express" : "2.5.0",
"clean-css" : "0.2.4", "clean-css" : "0.2.4",
"uglify-js" : "1.1.1", "uglify-js" : "1.1.1",
"gzip" : "0.1.0",
"formidable" : "1.0.7", "formidable" : "1.0.7",
"log4js" : "0.3.9", "log4js" : "0.4.1",
"jsdom-nocontextifiy" : "0.2.10", "jsdom-nocontextifiy" : "0.2.10",
"async-stacktrace" : "0.0.2" "async-stacktrace" : "0.0.2"
}, },
"devDependencies" : {
"jshint" : "0.5.5"
},
"version" : "1.0.0" "version" : "1.0.0"
} }