prepare to async: trivial reformatting

This change is only cosmetic. Its aim is do make it easier to understand the
async changes that are going to be merged later on. It was extracted from the
original work from Ray Bellis.

To verify that nothing has changed, you can run the following command on each
file touched by this commit:
  npm install uglify-es
  diff --unified <(uglify-js --beautify bracketize <BEFORE.js>) <(uglify-js --beautify bracketize <AFTER.js>)



This is a complete script that does the same automatically (works from a
mercurial clone):

```bash
#!/usr/bin/env bash

set -eu

REVISION=<THIS_REVISION>

PARENT_REV=$(hg identify --rev "${REVISION}" --template '{p1rev}')
FILE_LIST=$(hg status --no-status --change ${REVISION})
UGLIFYJS="node_modules/uglify-es/bin/uglifyjs"

for FILE_NAME in ${FILE_LIST[@]}; do
  echo "Checking ${FILE_NAME}"
  diff --unified \
    <("${UGLIFYJS}" --beautify bracketize <(hg cat --rev "${PARENT_REV}" "${FILE_NAME}")) \
    <("${UGLIFYJS}" --beautify bracketize <(hg cat --rev "${REVISION}"   "${FILE_NAME}"))
done
```
This commit is contained in:
muxator 2019-02-08 23:20:57 +01:00
parent cc23bd18a4
commit 9497ee734f
33 changed files with 2706 additions and 2943 deletions

View file

@ -1,109 +1,102 @@
/*
This is a debug tool. It checks all revisions for data corruption
*/
* This is a debug tool. It checks all revisions for data corruption
*/
if(process.argv.length != 2)
{
if (process.argv.length != 2) {
console.error("Use: node bin/checkAllPads.js");
process.exit(1);
}
//initialize the variables
// initialize the variables
var db, settings, padManager;
var npm = require("../src/node_modules/npm");
var async = require("../src/node_modules/async");
var npm = require('../src/node_modules/npm');
var async = require('../src/node_modules/async');
var Changeset = require("../src/static/js/Changeset");
var Changeset = require('../src/static/js/Changeset');
async.series([
//load npm
// load npm
function(callback) {
npm.load({}, callback);
},
//load modules
// load modules
function(callback) {
settings = require('../src/node/utils/Settings');
db = require('../src/node/db/DB');
//initialize the database
// initialize the database
db.init(callback);
},
//load pads
function (callback)
{
// load pads
function (callback) {
padManager = require('../src/node/db/PadManager');
padManager.listAllPads(function(err, res)
{
padManager.listAllPads(function(err, res) {
padIds = res.padIDs;
callback(err);
});
},
function (callback)
{
async.forEach(padIds, function(padId, callback)
{
function (callback) {
async.forEach(padIds, function(padId, callback) {
padManager.getPad(padId, function(err, pad) {
if (err) {
callback(err);
}
//check if the pad has a pool
if(pad.pool === undefined )
{
// check if the pad has a pool
if (pad.pool === undefined ) {
console.error("[" + pad.id + "] Missing attribute pool");
callback();
return;
}
//create an array with key kevisions
//key revisions always save the full pad atext
// create an array with key kevisions
// key revisions always save the full pad atext
var head = pad.getHeadRevisionNumber();
var keyRevisions = [];
for(var i=0;i<head;i+=100)
{
for (var i = 0; i < head; i += 100) {
keyRevisions.push(i);
}
//run trough all key revisions
async.forEachSeries(keyRevisions, function(keyRev, callback)
{
//create an array of revisions we need till the next keyRevision or the End
// run through all key revisions
async.forEachSeries(keyRevisions, function(keyRev, callback) {
// create an array of revisions we need till the next keyRevision or the End
var revisionsNeeded = [];
for(var i=keyRev;i<=keyRev+100 && i<=head; i++)
{
for(var i = keyRev; i <= keyRev + 100 && i <= head; i++) {
revisionsNeeded.push(i);
}
//this array will hold all revision changesets
// this array will hold all revision changesets
var revisions = [];
//run trough all needed revisions and get them from the database
async.forEach(revisionsNeeded, function(revNum, callback)
{
db.db.get("pad:"+pad.id+":revs:" + revNum, function(err, revision)
{
// run through all needed revisions and get them from the database
async.forEach(revisionsNeeded, function(revNum, callback) {
db.db.get("pad:" + pad.id + ":revs:" + revNum, function(err, revision) {
revisions[revNum] = revision;
callback(err);
});
}, function(err)
{
if(err)
{
},
function(err) {
if (err) {
callback(err);
return;
}
//check if the revision exists
// check if the revision exists
if (revisions[keyRev] == null) {
console.error("[" + pad.id + "] Missing revision " + keyRev);
callback();
return;
}
//check if there is a atext in the keyRevisions
if(revisions[keyRev].meta === undefined || revisions[keyRev].meta.atext === undefined)
{
// check if there is a atext in the keyRevisions
if (revisions[keyRev].meta === undefined || revisions[keyRev].meta.atext === undefined) {
console.error("[" + pad.id + "] Missing atext in revision " + keyRev);
callback();
return;
@ -112,16 +105,12 @@ async.series([
var apool = pad.pool;
var atext = revisions[keyRev].meta.atext;
for(var i=keyRev+1;i<=keyRev+100 && i<=head; i++)
{
try
{
//console.log("[" + pad.id + "] check revision " + i);
for(var i = keyRev + 1; i <= keyRev + 100 && i <= head; i++) {
try {
// console.log("[" + pad.id + "] check revision " + i);
var cs = revisions[i].changeset;
atext = Changeset.applyToAText(cs, atext, apool);
}
catch(e)
{
} catch(e) {
console.error("[" + pad.id + "] Bad changeset at revision " + i + " - " + e.message);
callback();
return;
@ -134,11 +123,11 @@ async.series([
});
}, callback);
}
], function (err)
{
if(err) throw err;
else
{
],
function (err) {
if (err) {
throw err;
} else {
console.log("finished");
process.exit(0);
}

View file

@ -1,107 +1,97 @@
/*
This is a debug tool. It checks all revisions for data corruption
*/
* This is a debug tool. It checks all revisions for data corruption
*/
if(process.argv.length != 3)
{
if (process.argv.length != 3) {
console.error("Use: node bin/checkPad.js $PADID");
process.exit(1);
}
//get the padID
var padId = process.argv[2];
//initialize the variables
// initialize the variables
var db, settings, padManager;
var npm = require("../src/node_modules/npm");
var async = require("../src/node_modules/async");
var npm = require('../src/node_modules/npm');
var async = require('../src/node_modules/async');
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var Changeset = require('ep_etherpad-lite/static/js/Changeset');
async.series([
//load npm
// load npm
function(callback) {
npm.load({}, function(er) {
callback(er);
})
});
},
//load modules
// load modules
function(callback) {
settings = require('../src/node/utils/Settings');
db = require('../src/node/db/DB');
//initialize the database
// initialize the database
db.init(callback);
},
//get the pad
function (callback)
{
// get the pad
function (callback) {
padManager = require('../src/node/db/PadManager');
padManager.doesPadExists(padId, function(err, exists)
{
if(!exists)
{
padManager.doesPadExists(padId, function(err, exists) {
if (!exists) {
console.error("Pad does not exist");
process.exit(1);
}
padManager.getPad(padId, function(err, _pad)
{
padManager.getPad(padId, function(err, _pad) {
pad = _pad;
callback(err);
});
});
},
function (callback)
{
//create an array with key revisions
//key revisions always save the full pad atext
function (callback) {
// create an array with key revisions
// key revisions always save the full pad atext
var head = pad.getHeadRevisionNumber();
var keyRevisions = [];
for(var i=0;i<head;i+=100)
{
for (var i = 0; i < head; i += 100) {
keyRevisions.push(i);
}
//run trough all key revisions
async.forEachSeries(keyRevisions, function(keyRev, callback)
{
//create an array of revisions we need till the next keyRevision or the End
// run through all key revisions
async.forEachSeries(keyRevisions, function(keyRev, callback) {
// create an array of revisions we need till the next keyRevision or the End
var revisionsNeeded = [];
for(var i=keyRev;i<=keyRev+100 && i<=head; i++)
{
for(var i = keyRev; i <= keyRev + 100 && i <= head; i++) {
revisionsNeeded.push(i);
}
//this array will hold all revision changesets
// this array will hold all revision changesets
var revisions = [];
//run trough all needed revisions and get them from the database
async.forEach(revisionsNeeded, function(revNum, callback)
{
db.db.get("pad:"+padId+":revs:" + revNum, function(err, revision)
{
// run through all needed revisions and get them from the database
async.forEach(revisionsNeeded, function(revNum, callback) {
db.db.get("pad:" + padId + ":revs:" + revNum, function(err, revision) {
revisions[revNum] = revision;
callback(err);
});
}, function(err)
{
if(err)
{
},
function(err) {
if (err) {
callback(err);
return;
}
//check if the pad has a pool
if(pad.pool === undefined )
{
// check if the pad has a pool
if (pad.pool === undefined) {
console.error("Attribute pool is missing");
process.exit(1);
}
//check if there is an atext in the keyRevisions
if(revisions[keyRev] === undefined || revisions[keyRev].meta === undefined || revisions[keyRev].meta.atext === undefined)
{
// check if there is an atext in the keyRevisions
if (revisions[keyRev] === undefined || revisions[keyRev].meta === undefined || revisions[keyRev].meta.atext === undefined) {
console.error("No atext in key revision " + keyRev);
callback();
return;
@ -110,16 +100,12 @@ async.series([
var apool = pad.pool;
var atext = revisions[keyRev].meta.atext;
for(var i=keyRev+1;i<=keyRev+100 && i<=head; i++)
{
try
{
//console.log("check revision " + i);
for (var i = keyRev + 1; i <= keyRev + 100 && i <= head; i++) {
try {
// console.log("check revision " + i);
var cs = revisions[i].changeset;
atext = Changeset.applyToAText(cs, atext, apool);
}
catch(e)
{
} catch(e) {
console.error("Bad changeset at revision " + i + " - " + e.message);
callback();
return;
@ -130,11 +116,11 @@ async.series([
});
}, callback);
}
], function (err)
{
if(err) throw err;
else
{
],
function (err) {
if(err) {
throw err;
} else {
console.log("finished");
process.exit(0);
}

View file

@ -1,63 +1,63 @@
/*
A tool for deleting pads from the CLI, because sometimes a brick is required to fix a window.
*/
* A tool for deleting pads from the CLI, because sometimes a brick is required
* to fix a window.
*/
if(process.argv.length != 3)
{
if (process.argv.length != 3) {
console.error("Use: node deletePad.js $PADID");
process.exit(1);
}
//get the padID
// get the padID
var padId = process.argv[2];
var db, padManager, pad, settings;
var neededDBValues = ["pad:"+padId];
var npm = require("../src/node_modules/npm");
var async = require("../src/node_modules/async");
var npm = require('../src/node_modules/npm');
var async = require('../src/node_modules/async');
async.series([
// load npm
function(callback) {
npm.load({}, function(er) {
if(er)
{
if (er) {
console.error("Could not load NPM: " + er)
process.exit(1);
}
else
{
} else {
callback();
}
})
});
},
// load modules
function(callback) {
settings = require('../src/node/utils/Settings');
db = require('../src/node/db/DB');
callback();
},
// initialize the database
function (callback)
{
function (callback) {
db.init(callback);
},
// delete the pad and its links
function (callback)
{
function (callback) {
padManager = require('../src/node/db/PadManager');
padManager.removePad(padId, function(err){
callback(err);
});
callback();
}
], function (err)
{
if(err) throw err;
else
{
console.log("Finished deleting padId: "+padId);
],
function (err) {
if(err) {
throw err;
} else {
console.log("Finished deleting padId: " + padId);
process.exit();
}
});

View file

@ -1,109 +1,97 @@
/*
This is a debug tool. It helps to extract all datas of a pad and move it from an productive environment and to a develop environment to reproduce bugs there. It outputs a dirtydb file
*/
* This is a debug tool. It helps to extract all datas of a pad and move it from
* a productive environment and to a develop environment to reproduce bugs
* there. It outputs a dirtydb file
*/
if(process.argv.length != 3)
{
if (process.argv.length != 3) {
console.error("Use: node extractPadData.js $PADID");
process.exit(1);
}
//get the padID
// get the padID
var padId = process.argv[2];
var db, dirty, padManager, pad, settings;
var neededDBValues = ["pad:"+padId];
var npm = require("../node_modules/ep_etherpad-lite/node_modules/npm");
var async = require("../node_modules/ep_etherpad-lite/node_modules/async");
var npm = require('../node_modules/ep_etherpad-lite/node_modules/npm');
var async = require('../node_modules/ep_etherpad-lite/node_modules/async');
async.series([
// load npm
function(callback) {
npm.load({}, function(er) {
if(er)
{
if (er) {
console.error("Could not load NPM: " + er)
process.exit(1);
}
else
{
} else {
callback();
}
})
},
// load modules
function(callback) {
settings = require('../node_modules/ep_etherpad-lite/node/utils/Settings');
db = require('../node_modules/ep_etherpad-lite/node/db/DB');
dirty = require("../node_modules/ep_etherpad-lite/node_modules/ueberDB/node_modules/dirty")(padId + ".db");
dirty = require('../node_modules/ep_etherpad-lite/node_modules/ueberDB/node_modules/dirty')(padId + ".db");
callback();
},
//initialize the database
function (callback)
{
// initialize the database
function (callback) {
db.init(callback);
},
//get the pad
function (callback)
{
// get the pad
function (callback) {
padManager = require('../node_modules/ep_etherpad-lite/node/db/PadManager');
padManager.getPad(padId, function(err, _pad)
{
padManager.getPad(padId, function(err, _pad) {
pad = _pad;
callback(err);
});
},
function (callback)
{
//add all authors
function (callback) {
// add all authors
var authors = pad.getAllAuthors();
for(var i=0;i<authors.length;i++)
{
neededDBValues.push("globalAuthor:" + authors[i]);
for (var i = 0; i < authors.length; i++) {
neededDBValues.push('globalAuthor:' + authors[i]);
}
//add all revisions
// add all revisions
var revHead = pad.head;
for(var i=0;i<=revHead;i++)
{
neededDBValues.push("pad:"+padId+":revs:" + i);
for (var i = 0; i <= revHead; i++) {
neededDBValues.push('pad:' + padId + ':revs:' + i);
}
//get all chat values
// get all chat values
var chatHead = pad.chatHead;
for(var i=0;i<=chatHead;i++)
{
neededDBValues.push("pad:"+padId+":chat:" + i);
for (var i = 0; i <= chatHead; i++) {
neededDBValues.push('pad:' + padId + ':chat:' + i);
}
//get and set all values
async.forEach(neededDBValues, function(dbkey, callback)
{
db.db.db.wrappedDB.get(dbkey, function(err, dbvalue)
{
if(err) { callback(err); return}
// get and set all values
async.forEach(neededDBValues, function(dbkey, callback) {
db.db.db.wrappedDB.get(dbkey, function(err, dbvalue) {
if (err) { callback(err); return}
if(dbvalue && typeof dbvalue != 'object'){
dbvalue=JSON.parse(dbvalue); // if it's not json then parse it as json
if (dbvalue && typeof dbvalue != 'object') {
dbvalue = JSON.parse(dbvalue); // if it's not json then parse it as json
}
dirty.set(dbkey, dbvalue, callback);
});
}, callback);
}
], function (err)
{
if(err) throw err;
else
{
],
function (err) {
if (err) {
throw err;
} else {
console.log("finished");
process.exit();
}
});
//get the pad object
//get all revisions of this pad
//get all authors related to this pad
//get the readonly link related to this pad
//get the chat entries related to this pad

File diff suppressed because it is too large Load diff

View file

@ -18,25 +18,33 @@
* limitations under the License.
*/
var ERR = require("async-stacktrace");
var db = require("./DB").db;
var customError = require("../utils/customError");
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
exports.getColorPalette = function(){
return ["#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", "#ffa8a8", "#ffe699", "#cfff9e", "#99ffb3", "#a3ffff", "#99b3ff", "#cc99ff", "#ff99e5", "#e7b1b1", "#e9dcAf", "#cde9af", "#bfedcc", "#b1e7e7", "#c3cdee", "#d2b8ea", "#eec3e6", "#e9cece", "#e7e0ca", "#d3e5c7", "#bce1c5", "#c1e2e2", "#c1c9e2", "#cfc1e2", "#e0bdd9", "#baded3", "#a0f8eb", "#b1e7e0", "#c3c8e4", "#cec5e2", "#b1d5e7", "#cda8f0", "#f0f0a8", "#f2f2a6", "#f5a8eb", "#c5f9a9", "#ececbb", "#e7c4bc", "#daf0b2", "#b0a0fd", "#bce2e7", "#cce2bb", "#ec9afe", "#edabbd", "#aeaeea", "#c4e7b1", "#d722bb", "#f3a5e7", "#ffa8a8", "#d8c0c5", "#eaaedd", "#adc6eb", "#bedad1", "#dee9af", "#e9afc2", "#f8d2a0", "#b3b3e6"];
exports.getColorPalette = function() {
return [
"#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1",
"#ffa8a8", "#ffe699", "#cfff9e", "#99ffb3", "#a3ffff", "#99b3ff", "#cc99ff", "#ff99e5",
"#e7b1b1", "#e9dcAf", "#cde9af", "#bfedcc", "#b1e7e7", "#c3cdee", "#d2b8ea", "#eec3e6",
"#e9cece", "#e7e0ca", "#d3e5c7", "#bce1c5", "#c1e2e2", "#c1c9e2", "#cfc1e2", "#e0bdd9",
"#baded3", "#a0f8eb", "#b1e7e0", "#c3c8e4", "#cec5e2", "#b1d5e7", "#cda8f0", "#f0f0a8",
"#f2f2a6", "#f5a8eb", "#c5f9a9", "#ececbb", "#e7c4bc", "#daf0b2", "#b0a0fd", "#bce2e7",
"#cce2bb", "#ec9afe", "#edabbd", "#aeaeea", "#c4e7b1", "#d722bb", "#f3a5e7", "#ffa8a8",
"#d8c0c5", "#eaaedd", "#adc6eb", "#bedad1", "#dee9af", "#e9afc2", "#f8d2a0", "#b3b3e6"
];
};
/**
* Checks if the author exists
*/
exports.doesAuthorExists = function (authorID, callback)
exports.doesAuthorExists = function(authorID, callback)
{
//check if the database entry of this author exists
db.get("globalAuthor:" + authorID, function (err, author)
{
if(ERR(err, callback)) return;
// check if the database entry of this author exists
db.get("globalAuthor:" + authorID, function(err, author) {
if (ERR(err, callback)) return;
callback(null, author != null);
});
}
@ -46,12 +54,12 @@ exports.doesAuthorExists = function (authorID, callback)
* @param {String} token The token
* @param {Function} callback callback (err, author)
*/
exports.getAuthor4Token = function (token, callback)
exports.getAuthor4Token = function(token, callback)
{
mapAuthorWithDBKey("token2author", token, function(err, author)
{
if(ERR(err, callback)) return;
//return only the sub value authorID
mapAuthorWithDBKey("token2author", token, function(err, author) {
if (ERR(err, callback)) return;
// return only the sub value authorID
callback(null, author ? author.authorID : author);
});
}
@ -62,17 +70,17 @@ exports.getAuthor4Token = function (token, callback)
* @param {String} name The name of the author (optional)
* @param {Function} callback callback (err, author)
*/
exports.createAuthorIfNotExistsFor = function (authorMapper, name, callback)
exports.createAuthorIfNotExistsFor = function(authorMapper, name, callback)
{
mapAuthorWithDBKey("mapper2author", authorMapper, function(err, author)
{
if(ERR(err, callback)) return;
mapAuthorWithDBKey("mapper2author", authorMapper, function(err, author) {
if (ERR(err, callback)) return;
//set the name of this author
if(name)
if (name) {
// set the name of this author
exports.setAuthorName(author.authorID, name);
}
//return the authorID
// return the authorID
callback(null, author);
});
}
@ -86,33 +94,30 @@ exports.createAuthorIfNotExistsFor = function (authorMapper, name, callback)
*/
function mapAuthorWithDBKey (mapperkey, mapper, callback)
{
//try to map to an author
db.get(mapperkey + ":" + mapper, function (err, author)
{
if(ERR(err, callback)) return;
// try to map to an author
db.get(mapperkey + ":" + mapper, function(err, author) {
if (ERR(err, callback)) return;
//there is no author with this mapper, so create one
if(author == null)
{
exports.createAuthor(null, function(err, author)
{
if(ERR(err, callback)) return;
if (author == null) {
// there is no author with this mapper, so create one
exports.createAuthor(null, function(err, author) {
if (ERR(err, callback)) return;
//create the token2author relation
// create the token2author relation
db.set(mapperkey + ":" + mapper, author.authorID);
//return the author
// return the author
callback(null, author);
});
return;
}
//there is a author with this mapper
//update the timestamp of this author
// there is an author with this mapper
// update the timestamp of this author
db.setSub("globalAuthor:" + author, ["timestamp"], Date.now());
//return the author
// return the author
callback(null, {authorID: author});
});
}
@ -123,13 +128,17 @@ function mapAuthorWithDBKey (mapperkey, mapper, callback)
*/
exports.createAuthor = function(name, callback)
{
//create the new author name
// create the new author name
var author = "a." + randomString(16);
//create the globalAuthors db entry
var authorObj = {"colorId" : Math.floor(Math.random()*(exports.getColorPalette().length)), "name": name, "timestamp": Date.now()};
// create the globalAuthors db entry
var authorObj = {
"colorId": Math.floor(Math.random() * (exports.getColorPalette().length)),
"name": name,
"timestamp": Date.now()
};
//set the global author db entry
// set the global author db entry
db.set("globalAuthor:" + author, authorObj);
callback(null, {authorID: author});
@ -140,19 +149,17 @@ exports.createAuthor = function(name, callback)
* @param {String} author The id of the author
* @param {Function} callback callback(err, authorObj)
*/
exports.getAuthor = function (author, callback)
exports.getAuthor = function(author, callback)
{
db.get("globalAuthor:" + author, callback);
}
/**
* Returns the color Id of the author
* @param {String} author The id of the author
* @param {Function} callback callback(err, colorId)
*/
exports.getAuthorColorId = function (author, callback)
exports.getAuthorColorId = function(author, callback)
{
db.getSub("globalAuthor:" + author, ["colorId"], callback);
}
@ -163,7 +170,7 @@ exports.getAuthorColorId = function (author, callback)
* @param {String} colorId The color id of the author
* @param {Function} callback (optional)
*/
exports.setAuthorColorId = function (author, colorId, callback)
exports.setAuthorColorId = function(author, colorId, callback)
{
db.setSub("globalAuthor:" + author, ["colorId"], colorId, callback);
}
@ -173,7 +180,7 @@ exports.setAuthorColorId = function (author, colorId, callback)
* @param {String} author The id of the author
* @param {Function} callback callback(err, name)
*/
exports.getAuthorName = function (author, callback)
exports.getAuthorName = function(author, callback)
{
db.getSub("globalAuthor:" + author, ["name"], callback);
}
@ -184,7 +191,7 @@ exports.getAuthorName = function (author, callback)
* @param {String} name The name of the author
* @param {Function} callback (optional)
*/
exports.setAuthorName = function (author, name, callback)
exports.setAuthorName = function(author, name, callback)
{
db.setSub("globalAuthor:" + author, ["name"], name, callback);
}
@ -194,33 +201,33 @@ exports.setAuthorName = function (author, name, callback)
* @param {String} author The id of the author
* @param {Function} callback (optional)
*/
exports.listPadsOfAuthor = function (authorID, callback)
exports.listPadsOfAuthor = function(authorID, callback)
{
/* There are two other places where this array is manipulated:
* (1) When the author is added to a pad, the author object is also updated
* (2) When a pad is deleted, each author of that pad is also updated
*/
//get the globalAuthor
db.get("globalAuthor:" + authorID, function(err, author)
{
if(ERR(err, callback)) return;
//author does not exists
if(author == null)
{
callback(new customError("authorID does not exist","apierror"))
// get the globalAuthor
db.get("globalAuthor:" + authorID, function(err, author) {
if (ERR(err, callback)) return;
if (author == null) {
// author does not exist
callback(new customError("authorID does not exist", "apierror"));
return;
}
//everything is fine, return the pad IDs
// everything is fine, return the pad IDs
var pads = [];
if(author.padIDs != null)
{
for (var padId in author.padIDs)
{
if (author.padIDs != null) {
for (var padId in author.padIDs) {
pads.push(padId);
}
}
callback(null, {padIDs: pads});
});
}
@ -230,24 +237,22 @@ exports.listPadsOfAuthor = function (authorID, callback)
* @param {String} author The id of the author
* @param {String} padID The id of the pad the author contributes to
*/
exports.addPad = function (authorID, padID)
exports.addPad = function(authorID, padID)
{
//get the entry
db.get("globalAuthor:" + authorID, function(err, author)
{
if(ERR(err)) return;
if(author == null) return;
// get the entry
db.get("globalAuthor:" + authorID, function(err, author) {
if (ERR(err)) return;
if (author == null) return;
//the entry doesn't exist so far, let's create it
if(author.padIDs == null)
{
if (author.padIDs == null) {
// the entry doesn't exist so far, let's create it
author.padIDs = {};
}
//add the entry for this pad
author.padIDs[padID] = 1;// anything, because value is not used
// add the entry for this pad
author.padIDs[padID] = 1; // anything, because value is not used
//save the new element back
// save the new element back
db.set("globalAuthor:" + authorID, author);
});
}
@ -257,16 +262,14 @@ exports.addPad = function (authorID, padID)
* @param {String} author The id of the author
* @param {String} padID The id of the pad the author contributes to
*/
exports.removePad = function (authorID, padID)
exports.removePad = function(authorID, padID)
{
db.get("globalAuthor:" + authorID, function (err, author)
{
if(ERR(err)) return;
if(author == null) return;
db.get("globalAuthor:" + authorID, function(err, author) {
if (ERR(err)) return;
if (author == null) return;
if(author.padIDs != null)
{
//remove pad from author
if (author.padIDs != null) {
// remove pad from author
delete author.padIDs[padID];
db.set("globalAuthor:" + authorID, author);
}

View file

@ -23,7 +23,7 @@ var ueberDB = require("ueberdb2");
var settings = require("../utils/Settings");
var log4js = require('log4js');
//set database settings
// set database settings
var db = new ueberDB.database(settings.dbType, settings.dbSettings, null, log4js.getLogger("ueberDB"));
/**
@ -35,21 +35,16 @@ exports.db = null;
* Initalizes the database with the settings provided by the settings module
* @param {Function} callback
*/
exports.init = function(callback)
{
//initalize the database async
db.init(function(err)
{
//there was an error while initializing the database, output it and stop
if(err)
{
exports.init = function(callback) {
// initalize the database async
db.init(function(err) {
if (err) {
// there was an error while initializing the database, output it and stop
console.error("ERROR: Problem while initalizing the database");
console.error(err.stack ? err.stack : err);
process.exit(1);
}
//everything ok
else
{
} else {
// everything ok
exports.db = db;
callback(null);
}

View file

@ -18,7 +18,6 @@
* limitations under the License.
*/
var ERR = require("async-stacktrace");
var customError = require("../utils/customError");
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
@ -29,16 +28,17 @@ var sessionManager = require("./SessionManager");
exports.listAllGroups = function(callback) {
db.get("groups", function (err, groups) {
if(ERR(err, callback)) return;
if (ERR(err, callback)) return;
if (groups == null) {
// there are no groups
if(groups == null) {
callback(null, {groupIDs: []});
return;
}
var groupIDs = [];
for ( var groupID in groups ) {
for (var groupID in groups) {
groupIDs.push(groupID);
}
callback(null, {groupIDs: groupIDs});
@ -50,96 +50,90 @@ exports.deleteGroup = function(groupID, callback)
var group;
async.series([
//ensure group exists
function (callback)
{
//try to get the group entry
db.get("group:" + groupID, function (err, _group)
{
if(ERR(err, callback)) return;
// ensure group exists
function (callback) {
// try to get the group entry
db.get("group:" + groupID, function (err, _group) {
if (ERR(err, callback)) return;
//group does not exist
if(_group == null)
{
callback(new customError("groupID does not exist","apierror"));
if (_group == null) {
// group does not exist
callback(new customError("groupID does not exist", "apierror"));
return;
}
//group exists, everything is fine
// group exists, everything is fine
group = _group;
callback();
});
},
//iterate trough all pads of this groups and delete them
function(callback)
{
//collect all padIDs in an array, that allows us to use async.forEach
// iterate through all pads of this group and delete them
function(callback) {
// collect all padIDs in an array, that allows us to use async.forEach
var padIDs = [];
for(var i in group.pads)
{
for(var i in group.pads) {
padIDs.push(i);
}
//loop trough all pads and delete them
async.forEach(padIDs, function(padID, callback)
{
padManager.getPad(padID, function(err, pad)
{
if(ERR(err, callback)) return;
// loop through all pads and delete them
async.forEach(padIDs, function(padID, callback) {
padManager.getPad(padID, function(err, pad) {
if (ERR(err, callback)) return;
pad.remove(callback);
});
}, callback);
},
//iterate trough group2sessions and delete all sessions
// iterate through group2sessions and delete all sessions
function(callback)
{
//try to get the group entry
db.get("group2sessions:" + groupID, function (err, group2sessions)
{
if(ERR(err, callback)) return;
// try to get the group entry
db.get("group2sessions:" + groupID, function (err, group2sessions) {
if (ERR(err, callback)) return;
//skip if there is no group2sessions entry
if(group2sessions == null) {callback(); return}
// skip if there is no group2sessions entry
if (group2sessions == null) { callback(); return }
//collect all sessions in an array, that allows us to use async.forEach
// collect all sessions in an array, that allows us to use async.forEach
var sessions = [];
for(var i in group2sessions.sessionsIDs)
{
for (var i in group2sessions.sessionsIDs) {
sessions.push(i);
}
//loop trough all sessions and delete them
async.forEach(sessions, function(session, callback)
{
// loop through all sessions and delete them
async.forEach(sessions, function(session, callback) {
sessionManager.deleteSession(session, callback);
}, callback);
});
},
//remove group and group2sessions entry
function(callback)
{
// remove group and group2sessions entry
function(callback) {
db.remove("group2sessions:" + groupID);
db.remove("group:" + groupID);
callback();
},
//unlist the group
function(callback)
{
// unlist the group
function(callback) {
exports.listAllGroups(function(err, groups) {
if(ERR(err, callback)) return;
if (ERR(err, callback)) return;
groups = groups? groups.groupIDs : [];
if (groups.indexOf(groupID) == -1) {
// it's not listed
if(groups.indexOf(groupID) == -1) {
callback();
return;
}
// remove from the list
groups.splice(groups.indexOf(groupID), 1);
// store empty groupe list
if(groups.length == 0) {
// store empty group list
if (groups.length == 0) {
db.set("groups", {});
callback();
return;
@ -150,42 +144,42 @@ exports.deleteGroup = function(groupID, callback)
async.forEach(groups, function(group, cb) {
newGroups[group] = 1;
cb();
},function() {
},
function() {
db.set("groups", newGroups);
callback();
});
});
}
], function(err)
{
if(ERR(err, callback)) return;
],
function(err) {
if (ERR(err, callback)) return;
callback();
});
}
exports.doesGroupExist = function(groupID, callback)
{
//try to get the group entry
db.get("group:" + groupID, function (err, group)
{
if(ERR(err, callback)) return;
// try to get the group entry
db.get("group:" + groupID, function (err, group) {
if (ERR(err, callback)) return;
callback(null, group != null);
});
}
exports.createGroup = function(callback)
{
//search for non existing groupID
// search for non existing groupID
var groupID = "g." + randomString(16);
//create the group
// create the group
db.set("group:" + groupID, {pads: {}});
//list the group
// list the group
exports.listAllGroups(function(err, groups) {
if(ERR(err, callback)) return;
groups = groups? groups.groupIDs : [];
if (ERR(err, callback)) return;
groups = groups? groups.groupIDs : [];
groups.push(groupID);
// regenerate group list
@ -193,7 +187,8 @@ exports.createGroup = function(callback)
async.forEach(groups, function(group, cb) {
newGroups[group] = 1;
cb();
},function() {
},
function() {
db.set("groups", newGroups);
callback(null, {groupID: groupID});
});
@ -202,129 +197,121 @@ exports.createGroup = function(callback)
exports.createGroupIfNotExistsFor = function(groupMapper, callback)
{
//ensure mapper is optional
if(typeof groupMapper != "string")
{
callback(new customError("groupMapper is no string","apierror"));
// ensure mapper is optional
if (typeof groupMapper != "string") {
callback(new customError("groupMapper is no string", "apierror"));
return;
}
//try to get a group for this mapper
db.get("mapper2group:"+groupMapper, function(err, groupID)
{
// try to get a group for this mapper
db.get("mapper2group:" + groupMapper, function(err, groupID) {
function createGroupForMapper(cb) {
exports.createGroup(function(err, responseObj)
{
if(ERR(err, cb)) return;
exports.createGroup(function(err, responseObj) {
if (ERR(err, cb)) return;
//create the mapper entry for this group
db.set("mapper2group:"+groupMapper, responseObj.groupID);
// create the mapper entry for this group
db.set("mapper2group:" + groupMapper, responseObj.groupID);
cb(null, responseObj);
});
}
if(ERR(err, callback)) return;
if (ERR(err, callback)) return;
if (groupID) {
// there is a group for this mapper
if(groupID) {
exports.doesGroupExist(groupID, function(err, exists) {
if(ERR(err, callback)) return;
if(exists) return callback(null, {groupID: groupID});
if (ERR(err, callback)) return;
if (exists) return callback(null, {groupID: groupID});
// hah, the returned group doesn't exist, let's create one
createGroupForMapper(callback)
})
});
return;
}
//there is no group for this mapper, let's create a group
// there is no group for this mapper, let's create a group
createGroupForMapper(callback)
});
}
exports.createGroupPad = function(groupID, padName, text, callback)
{
//create the padID
// create the padID
var padID = groupID + "$" + padName;
async.series([
//ensure group exists
function (callback)
{
exports.doesGroupExist(groupID, function(err, exists)
{
if(ERR(err, callback)) return;
// ensure group exists
function (callback) {
exports.doesGroupExist(groupID, function(err, exists) {
if (ERR(err, callback)) return;
//group does not exist
if(exists == false)
{
callback(new customError("groupID does not exist","apierror"));
if (exists == false) {
// group does not exist
callback(new customError("groupID does not exist", "apierror"));
return;
}
//group exists, everything is fine
// group exists, everything is fine
callback();
});
},
//ensure pad does not exists
function (callback)
{
padManager.doesPadExists(padID, function(err, exists)
{
if(ERR(err, callback)) return;
//pad exists already
if(exists == true)
{
callback(new customError("padName does already exist","apierror"));
// ensure pad doesn't exist already
function (callback) {
padManager.doesPadExists(padID, function(err, exists) {
if (ERR(err, callback)) return;
if (exists == true) {
// pad exists already
callback(new customError("padName does already exist", "apierror"));
return;
}
//pad does not exist, everything is fine
// pad does not exist, everything is fine
callback();
});
},
//create the pad
function (callback)
{
padManager.getPad(padID, text, function(err)
{
if(ERR(err, callback)) return;
// create the pad
function (callback) {
padManager.getPad(padID, text, function(err) {
if (ERR(err, callback)) return;
callback();
});
},
//create an entry in the group for this pad
function (callback)
{
// create an entry in the group for this pad
function (callback) {
db.setSub("group:" + groupID, ["pads", padID], 1);
callback();
}
], function(err)
{
if(ERR(err, callback)) return;
],
function(err) {
if (ERR(err, callback)) return;
callback(null, {padID: padID});
});
}
exports.listPads = function(groupID, callback)
{
exports.doesGroupExist(groupID, function(err, exists)
{
if(ERR(err, callback)) return;
exports.doesGroupExist(groupID, function(err, exists) {
if (ERR(err, callback)) return;
//group does not exist
if(exists == false)
{
callback(new customError("groupID does not exist","apierror"));
// ensure the group exists
if (exists == false) {
callback(new customError("groupID does not exist", "apierror"));
return;
}
//group exists, let's get the pads
db.getSub("group:" + groupID, ["pads"], function(err, result)
{
if(ERR(err, callback)) return;
// group exists, let's get the pads
db.getSub("group:" + groupID, ["pads"], function(err, result) {
if (ERR(err, callback)) return;
var pads = [];
for ( var padId in result ) {
pads.push(padId);

View file

@ -19,7 +19,7 @@ var crypto = require("crypto");
var randomString = require("../utils/randomstring");
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
//serialization/deserialization attributes
// serialization/deserialization attributes
var attributeBlackList = ["id"];
var jsonableList = ["pool"];
@ -33,7 +33,6 @@ exports.cleanText = function (txt) {
var Pad = function Pad(id) {
this.atext = Changeset.makeAText("\n");
this.pool = new AttributePool();
this.head = -1;
@ -60,7 +59,7 @@ Pad.prototype.getSavedRevisionsNumber = function getSavedRevisionsNumber() {
Pad.prototype.getSavedRevisionsList = function getSavedRevisionsList() {
var savedRev = new Array();
for(var rev in this.savedRevisions){
for (var rev in this.savedRevisions) {
savedRev.push(this.savedRevisions[rev].revNum);
}
savedRev.sort(function(a, b) {
@ -74,8 +73,9 @@ Pad.prototype.getPublicStatus = function getPublicStatus() {
};
Pad.prototype.appendRevision = function appendRevision(aChangeset, author) {
if(!author)
if (!author) {
author = '';
}
var newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool);
Changeset.copyAText(newAText, this.atext);
@ -88,21 +88,22 @@ Pad.prototype.appendRevision = function appendRevision(aChangeset, author) {
newRevData.meta.author = author;
newRevData.meta.timestamp = Date.now();
//ex. getNumForAuthor
if(author != '')
// ex. getNumForAuthor
if (author != '') {
this.pool.putAttrib(['author', author || '']);
}
if(newRev % 100 == 0)
{
if (newRev % 100 == 0) {
newRevData.meta.atext = this.atext;
}
db.set("pad:"+this.id+":revs:"+newRev, newRevData);
db.set("pad:" + this.id + ":revs:" + newRev, newRevData);
this.saveToDatabase();
// set the author to pad
if(author)
if (author) {
authorManager.addPad(author, this.id);
}
if (this.head == 0) {
hooks.callAll("padCreate", {'pad':this, 'author': author});
@ -111,49 +112,47 @@ Pad.prototype.appendRevision = function appendRevision(aChangeset, author) {
}
};
//save all attributes to the database
Pad.prototype.saveToDatabase = function saveToDatabase(){
// save all attributes to the database
Pad.prototype.saveToDatabase = function saveToDatabase() {
var dbObject = {};
for(var attr in this){
if(typeof this[attr] === "function") continue;
if(attributeBlackList.indexOf(attr) !== -1) continue;
for (var attr in this) {
if (typeof this[attr] === "function") continue;
if (attributeBlackList.indexOf(attr) !== -1) continue;
dbObject[attr] = this[attr];
if(jsonableList.indexOf(attr) !== -1){
if (jsonableList.indexOf(attr) !== -1) {
dbObject[attr] = dbObject[attr].toJsonable();
}
}
db.set("pad:"+this.id, dbObject);
db.set("pad:" + this.id, dbObject);
}
// get time of last edit (changeset application)
Pad.prototype.getLastEdit = function getLastEdit(callback){
Pad.prototype.getLastEdit = function getLastEdit(callback) {
var revNum = this.getHeadRevisionNumber();
db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "timestamp"], callback);
db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "timestamp"], callback);
}
Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum, callback) {
db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback);
db.getSub("pad:" + this.id + ":revs:" + revNum, ["changeset"], callback);
};
Pad.prototype.getRevisionAuthor = function getRevisionAuthor(revNum, callback) {
db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "author"], callback);
db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "author"], callback);
};
Pad.prototype.getRevisionDate = function getRevisionDate(revNum, callback) {
db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "timestamp"], callback);
db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "timestamp"], callback);
};
Pad.prototype.getAllAuthors = function getAllAuthors() {
var authors = [];
for(var key in this.pool.numToAttrib)
{
if(this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "")
{
for(var key in this.pool.numToAttrib) {
if (this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "") {
authors.push(this.pool.numToAttrib[key][1]);
}
}
@ -168,26 +167,22 @@ Pad.prototype.getInternalRevisionAText = function getInternalRevisionAText(targe
var atext;
var changesets = [];
//find out which changesets are needed
// find out which changesets are needed
var neededChangesets = [];
var curRev = keyRev;
while (curRev < targetRev)
{
while (curRev < targetRev) {
curRev++;
neededChangesets.push(curRev);
}
async.series([
//get all needed data out of the database
function(callback)
{
// get all needed data out of the database
function(callback) {
async.parallel([
//get the atext of the key revision
function (callback)
{
db.getSub("pad:"+_this.id+":revs:"+keyRev, ["meta", "atext"], function(err, _atext)
{
if(ERR(err, callback)) return;
// get the atext of the key revision
function (callback) {
db.getSub("pad:" + _this.id + ":revs:" + keyRev, ["meta", "atext"], function(err, _atext) {
if (ERR(err, callback)) return;
try {
atext = Changeset.cloneAText(_atext);
} catch (e) {
@ -197,14 +192,12 @@ Pad.prototype.getInternalRevisionAText = function getInternalRevisionAText(targe
callback();
});
},
//get all needed changesets
function (callback)
{
async.forEach(neededChangesets, function(item, callback)
{
_this.getRevisionChangeset(item, function(err, changeset)
{
if(ERR(err, callback)) return;
// get all needed changesets
function (callback) {
async.forEach(neededChangesets, function(item, callback) {
_this.getRevisionChangeset(item, function(err, changeset) {
if (ERR(err, callback)) return;
changesets[item] = changeset;
callback();
});
@ -212,52 +205,53 @@ Pad.prototype.getInternalRevisionAText = function getInternalRevisionAText(targe
}
], callback);
},
//apply all changesets to the key changeset
function(callback)
{
// apply all changesets to the key changeset
function(callback) {
var apool = _this.apool();
var curRev = keyRev;
while (curRev < targetRev)
{
while (curRev < targetRev) {
curRev++;
var cs = changesets[curRev];
try{
try {
atext = Changeset.applyToAText(cs, atext, apool);
}catch(e) {
} catch(e) {
return callback(e)
}
}
callback(null);
}
], function(err)
{
if(ERR(err, callback)) return;
],
function(err) {
if (ERR(err, callback)) return;
callback(null, atext);
});
};
Pad.prototype.getRevision = function getRevisionChangeset(revNum, callback) {
db.get("pad:"+this.id+":revs:"+revNum, callback);
db.get("pad:" + this.id + ":revs:" + revNum, callback);
};
Pad.prototype.getAllAuthorColors = function getAllAuthorColors(callback){
Pad.prototype.getAllAuthorColors = function getAllAuthorColors(callback) {
var authors = this.getAllAuthors();
var returnTable = {};
var colorPalette = authorManager.getColorPalette();
async.forEach(authors, function(author, callback){
authorManager.getAuthorColorId(author, function(err, colorId){
if(err){
async.forEach(authors, function(author, callback) {
authorManager.getAuthorColorId(author, function(err, colorId) {
if (err) {
return callback(err);
}
//colorId might be a hex color or an number out of the palette
returnTable[author]=colorPalette[colorId] || colorId;
// colorId might be a hex color or an number out of the palette
returnTable[author] = colorPalette[colorId] || colorId;
callback();
});
}, function(err){
},
function(err) {
callback(err, returnTable);
});
};
@ -266,15 +260,18 @@ Pad.prototype.getValidRevisionRange = function getValidRevisionRange(startRev, e
startRev = parseInt(startRev, 10);
var head = this.getHeadRevisionNumber();
endRev = endRev ? parseInt(endRev, 10) : head;
if(isNaN(startRev) || startRev < 0 || startRev > head) {
if (isNaN(startRev) || startRev < 0 || startRev > head) {
startRev = null;
}
if(isNaN(endRev) || endRev < startRev) {
if (isNaN(endRev) || endRev < startRev) {
endRev = null;
} else if(endRev > head) {
} else if (endRev > head) {
endRev = head;
}
if(startRev !== null && endRev !== null) {
if (startRev !== null && endRev !== null) {
return { startRev: startRev , endRev: endRev }
}
return null;
@ -289,12 +286,12 @@ Pad.prototype.text = function text() {
};
Pad.prototype.setText = function setText(newText) {
//clean the new text
// clean the new text
newText = exports.cleanText(newText);
var oldText = this.text();
//create the changeset
// create the changeset
// We want to ensure the pad still ends with a \n, but otherwise keep
// getText() and setText() consistent.
var changeset;
@ -304,27 +301,27 @@ Pad.prototype.setText = function setText(newText) {
changeset = Changeset.makeSplice(oldText, 0, oldText.length-1, newText);
}
//append the changeset
// append the changeset
this.appendRevision(changeset);
};
Pad.prototype.appendText = function appendText(newText) {
//clean the new text
// clean the new text
newText = exports.cleanText(newText);
var oldText = this.text();
//create the changeset
// create the changeset
var changeset = Changeset.makeSplice(oldText, oldText.length, 0, newText);
//append the changeset
// append the changeset
this.appendRevision(changeset);
};
Pad.prototype.appendChatMessage = function appendChatMessage(text, userId, time) {
this.chatHead++;
//save the chat entry in the database
db.set("pad:"+this.id+":chat:"+this.chatHead, {"text": text, "userId": userId, "time": time});
// save the chat entry in the database
db.set("pad:" + this.id + ":chat:" + this.chatHead, { "text": text, "userId": userId, "time": time });
this.saveToDatabase();
};
@ -333,78 +330,71 @@ Pad.prototype.getChatMessage = function getChatMessage(entryNum, callback) {
var entry;
async.series([
//get the chat entry
function(callback)
{
db.get("pad:"+_this.id+":chat:"+entryNum, function(err, _entry)
{
if(ERR(err, callback)) return;
// get the chat entry
function(callback) {
db.get("pad:" + _this.id + ":chat:" + entryNum, function(err, _entry) {
if (ERR(err, callback)) return;
entry = _entry;
callback();
});
},
//add the authorName
function(callback)
{
//this chat message doesn't exist, return null
if(entry == null)
{
// add the authorName
function(callback) {
// this chat message doesn't exist, return null
if (entry == null) {
callback();
return;
}
//get the authorName
authorManager.getAuthorName(entry.userId, function(err, authorName)
{
if(ERR(err, callback)) return;
// get the authorName
authorManager.getAuthorName(entry.userId, function(err, authorName) {
if (ERR(err, callback)) return;
entry.userName = authorName;
callback();
});
}
], function(err)
{
if(ERR(err, callback)) return;
],
function(err) {
if (ERR(err, callback)) return;
callback(null, entry);
});
};
Pad.prototype.getChatMessages = function getChatMessages(start, end, callback) {
//collect the numbers of chat entries and in which order we need them
// collect the numbers of chat entries and in which order we need them
var neededEntries = [];
var order = 0;
for(var i=start;i<=end; i++)
{
neededEntries.push({entryNum:i, order: order});
for (var i = start; i <= end; i++) {
neededEntries.push({ entryNum: i, order: order });
order++;
}
var _this = this;
//get all entries out of the database
// get all entries out of the database
var entries = [];
async.forEach(neededEntries, function(entryObject, callback)
{
_this.getChatMessage(entryObject.entryNum, function(err, entry)
{
if(ERR(err, callback)) return;
async.forEach(neededEntries, function(entryObject, callback) {
_this.getChatMessage(entryObject.entryNum, function(err, entry) {
if (ERR(err, callback)) return;
entries[entryObject.order] = entry;
callback();
});
}, function(err)
{
if(ERR(err, callback)) return;
},
function(err) {
if (ERR(err, callback)) return;
//sort out broken chat entries
//it looks like in happend in the past that the chat head was
//incremented, but the chat message wasn't added
// sort out broken chat entries
// it looks like in happened in the past that the chat head was
// incremented, but the chat message wasn't added
var cleanedEntries = [];
for(var i=0;i<entries.length;i++)
{
if(entries[i]!=null)
for (var i=0; i < entries.length; i++) {
if (entries[i] != null) {
cleanedEntries.push(entries[i]);
else
} else {
console.warn("WARNING: Found broken chat entry in pad " + _this.id);
}
}
callback(null, cleanedEntries);
});
@ -413,38 +403,33 @@ Pad.prototype.getChatMessages = function getChatMessages(start, end, callback) {
Pad.prototype.init = function init(text, callback) {
var _this = this;
//replace text with default text if text isn't set
if(text == null)
{
// 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;
// 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)
{
//copy all attr. To a transfrom via fromJsonable if necassary
for(var attr in value){
if(jsonableList.indexOf(attr) !== -1){
// if this pad exists, load it
if (value != null) {
// copy all attr. To a transfrom via fromJsonable if necessary
for (var attr in value) {
if (jsonableList.indexOf(attr) !== -1) {
_this[attr] = _this[attr].fromJsonable(value[attr]);
} else {
_this[attr] = value[attr];
}
}
}
//this pad doesn't exist, so create it
else
{
} else {
// this pad doesn't exist, so create it
var firstChangeset = Changeset.makeSplice("\n", 0, 0, exports.cleanText(text));
_this.appendRevision(firstChangeset, '');
}
hooks.callAll("padLoad", {'pad':_this});
hooks.callAll("padLoad", { 'pad': _this });
callback(null);
});
};
@ -471,51 +456,44 @@ Pad.prototype.copy = function copy(destinationID, force, callback) {
async.series([
// if it's a group pad, let's make sure the group exists.
function(callback)
{
if (destinationID.indexOf("$") === -1)
{
function(callback) {
if (destinationID.indexOf("$") === -1) {
callback();
return;
}
destGroupID = destinationID.split("$")[0]
groupManager.doesGroupExist(destGroupID, function (err, exists)
{
if(ERR(err, callback)) return;
destGroupID = destinationID.split("$")[0];
groupManager.doesGroupExist(destGroupID, function (err, exists) {
if (ERR(err, callback)) return;
//group does not exist
if(exists == false)
{
callback(new customError("groupID does not exist for destinationID","apierror"));
// group does not exist
if (exists == false) {
callback(new customError("groupID does not exist for destinationID", "apierror"));
return;
}
//everything is fine, continue
// everything is fine, continue
callback();
});
},
// if the pad exists, we should abort, unless forced.
function(callback)
{
padManager.doesPadExists(destinationID, function (err, exists)
{
if(ERR(err, callback)) return;
function(callback) {
padManager.doesPadExists(destinationID, function (err, exists) {
if (ERR(err, callback)) return;
/*
* this is the negation of a truthy comparison. Has been left in this
* wonky state to keep the old (possibly buggy) behaviour
*/
if (!(exists == true))
{
if (!(exists == true)) {
callback();
return;
}
if (!force)
{
if (!force) {
console.error("erroring out without force");
callback(new customError("destinationID already exists","apierror"));
callback(new customError("destinationID already exists", "apierror"));
console.error("erroring out without force - after");
return;
}
@ -527,54 +505,50 @@ Pad.prototype.copy = function copy(destinationID, force, callback) {
});
});
},
// copy the 'pad' entry
function(callback)
{
db.get("pad:"+sourceID, function(err, pad) {
db.set("pad:"+destinationID, pad);
function(callback) {
db.get("pad:" + sourceID, function(err, pad) {
db.set("pad:" + destinationID, pad);
});
callback();
},
//copy all relations
function(callback)
{
// copy all relations
function(callback) {
async.parallel([
//copy all chat messages
function(callback)
{
// copy all chat messages
function(callback) {
var chatHead = _this.chatHead;
for(var i=0;i<=chatHead;i++)
{
db.get("pad:"+sourceID+":chat:"+i, function (err, chat) {
for (var i=0; i <= chatHead; i++) {
db.get("pad:" + sourceID + ":chat:" + i, function (err, chat) {
if (ERR(err, callback)) return;
db.set("pad:"+destinationID+":chat:"+i, chat);
db.set("pad:" + destinationID + ":chat:" + i, chat);
});
}
callback();
},
//copy all revisions
function(callback)
{
// copy all revisions
function(callback) {
var revHead = _this.head;
for(var i=0;i<=revHead;i++)
{
db.get("pad:"+sourceID+":revs:"+i, function (err, rev) {
for (var i=0; i <= revHead; i++) {
db.get("pad:" + sourceID + ":revs:" + i, function (err, rev) {
if (ERR(err, callback)) return;
db.set("pad:"+destinationID+":revs:"+i, rev);
db.set("pad:" + destinationID + ":revs:" + i, rev);
});
}
callback();
},
//add the new pad to all authors who contributed to the old one
function(callback)
{
// add the new pad to all authors who contributed to the old one
function(callback) {
var authorIDs = _this.getAllAuthors();
authorIDs.forEach(function (authorID)
{
authorIDs.forEach(function (authorID) {
authorManager.addPad(authorID, destinationID);
});
@ -583,25 +557,29 @@ Pad.prototype.copy = function copy(destinationID, force, callback) {
// parallel
], callback);
},
function(callback) {
if (destGroupID) {
// Group pad? Add it to the group's list
if(destGroupID) db.setSub("group:" + destGroupID, ["pads", destinationID], 1);
db.setSub("group:" + destGroupID, ["pads", destinationID], 1);
}
// Initialize the new pad (will update the listAllPads cache)
setTimeout(function(){
setTimeout(function() {
padManager.getPad(destinationID, null, callback) // this runs too early.
},10);
}, 10);
},
// let the plugins know the pad was copied
function(callback) {
hooks.callAll('padCopy', { 'originalPad': _this, 'destinationID': destinationID });
callback();
}
// series
], function(err)
{
if(ERR(err, callback)) return;
callback(null, {padID: destinationID});
],
function(err) {
if (ERR(err, callback)) return;
callback(null, { padID: destinationID });
});
};
@ -609,46 +587,41 @@ Pad.prototype.remove = function remove(callback) {
var padID = this.id;
var _this = this;
//kick everyone from this pad
// kick everyone from this pad
padMessageHandler.kickSessionsFromPad(padID);
async.series([
//delete all relations
function(callback)
{
// delete all relations
function(callback) {
async.parallel([
//is it a group pad? -> delete the entry of this pad in the group
function(callback)
{
if(padID.indexOf("$") === -1)
{
// is it a group pad? -> delete the entry of this pad in the group
function(callback) {
if (padID.indexOf("$") === -1) {
// it isn't a group pad, nothing to do here
callback();
return;
}
// it is a group pad
var groupID = padID.substring(0,padID.indexOf("$"));
var groupID = padID.substring(0, padID.indexOf("$"));
db.get("group:" + groupID, function (err, group)
{
if(ERR(err, callback)) return;
db.get("group:" + groupID, function (err, group) {
if (ERR(err, callback)) return;
//remove the pad entry
// remove the pad entry
delete group.pads[padID];
//set the new value
// set the new value
db.set("group:" + groupID, group);
callback();
});
},
//remove the readonly entries
function(callback)
{
readOnlyManager.getReadOnlyId(padID, function(err, readonlyID)
{
if(ERR(err, callback)) return;
// remove the readonly entries
function(callback) {
readOnlyManager.getReadOnlyId(padID, function(err, readonlyID) {
if (ERR(err, callback)) return;
db.remove("pad2readonly:" + padID);
db.remove("readonly2pad:" + readonlyID);
@ -656,37 +629,34 @@ Pad.prototype.remove = function remove(callback) {
callback();
});
},
//delete all chat messages
function(callback)
{
// delete all chat messages
function(callback) {
var chatHead = _this.chatHead;
for(var i=0;i<=chatHead;i++)
{
db.remove("pad:"+padID+":chat:"+i);
for (var i = 0; i <= chatHead; i++) {
db.remove("pad:" + padID + ":chat:" + i);
}
callback();
},
//delete all revisions
function(callback)
{
// delete all revisions
function(callback) {
var revHead = _this.head;
for(var i=0;i<=revHead;i++)
{
db.remove("pad:"+padID+":revs:"+i);
for (var i = 0; i <= revHead; i++) {
db.remove("pad:" + padID + ":revs:" + i);
}
callback();
},
//remove pad from all authors who contributed
function(callback)
{
// remove pad from all authors who contributed
function(callback) {
var authorIDs = _this.getAllAuthors();
authorIDs.forEach(function (authorID)
{
authorIDs.forEach(function (authorID) {
authorManager.removePad(authorID, padID);
});
@ -694,20 +664,21 @@ Pad.prototype.remove = function remove(callback) {
}
], callback);
},
//delete the pad entry and delete pad from padManager
function(callback)
{
// delete the pad entry and delete pad from padManager
function(callback) {
padManager.removePad(padID);
hooks.callAll("padRemove", {'padID':padID});
hooks.callAll("padRemove", { 'padID': padID });
callback();
}
], function(err)
{
if(ERR(err, callback)) return;
],
function(err) {
if (ERR(err, callback)) return;
callback();
});
};
//set in db
// set in db
Pad.prototype.setPublicStatus = function setPublicStatus(publicStatus) {
this.publicStatus = publicStatus;
this.saveToDatabase();
@ -727,14 +698,14 @@ Pad.prototype.isPasswordProtected = function isPasswordProtected() {
};
Pad.prototype.addSavedRevision = function addSavedRevision(revNum, savedById, label) {
//if this revision is already saved, return silently
for(var i in this.savedRevisions){
if(this.savedRevisions[i] && this.savedRevisions[i].revNum === revNum){
// if this revision is already saved, return silently
for (var i in this.savedRevisions) {
if (this.savedRevisions[i] && this.savedRevisions[i].revNum === revNum) {
return;
}
}
//build the saved revision object
// build the saved revision object
var savedRevision = {};
savedRevision.revNum = revNum;
savedRevision.savedById = savedById;
@ -742,7 +713,7 @@ Pad.prototype.addSavedRevision = function addSavedRevision(revNum, savedById, la
savedRevision.timestamp = Date.now();
savedRevision.id = randomString(10);
//save this new saved revision
// save this new saved revision
this.savedRevisions.push(savedRevision);
this.saveToDatabase();
};
@ -753,19 +724,17 @@ Pad.prototype.getSavedRevisions = function getSavedRevisions() {
/* Crypto helper methods */
function hash(password, salt)
{
function hash(password, salt) {
var shasum = crypto.createHash('sha512');
shasum.update(password + salt);
return shasum.digest("hex") + "$" + salt;
}
function generateSalt()
{
function generateSalt() {
return randomString(86);
}
function compare(hashStr, password)
{
function compare(hashStr, password) {
return hash(password, hashStr.split("$")[1]) === hashStr;
}

View file

@ -35,12 +35,11 @@ var db = require("./DB").db;
* that's defined somewhere more sensible.
*/
var globalPads = {
get: function (name) { return this[':'+name]; },
set: function (name, value)
{
get: function(name) { return this[':'+name]; },
set: function(name, value) {
this[':'+name] = value;
},
remove: function (name) {
remove: function(name) {
delete this[':'+name];
}
};
@ -54,56 +53,63 @@ var padList = {
list: [],
sorted : false,
initiated: false,
init: function(cb)
{
db.findKeys("pad:*", "*:*:*", function(err, dbData)
{
if(ERR(err, cb)) return;
if(dbData != null){
init: function(cb) {
db.findKeys("pad:*", "*:*:*", function(err, dbData) {
if (ERR(err, cb)) return;
if (dbData != null) {
padList.initiated = true
dbData.forEach(function(val){
dbData.forEach(function(val) {
padList.addPad(val.replace(/pad:/,""),false);
});
cb && cb()
cb && cb();
}
});
return this;
},
load: function(cb) {
if(this.initiated) cb && cb()
else this.init(cb)
if (this.initiated) {
cb && cb();
} else {
this.init(cb);
}
},
/**
* Returns all pads in alphabetical order as array.
*/
getPads: function(cb){
getPads: function(cb) {
this.load(function() {
if(!padList.sorted){
if (!padList.sorted) {
padList.list = padList.list.sort();
padList.sorted = true;
}
cb && cb(padList.list);
})
},
addPad: function(name)
{
if(!this.initiated) return;
if(this.list.indexOf(name) == -1){
addPad: function(name) {
if (!this.initiated) return;
if (this.list.indexOf(name) == -1) {
this.list.push(name);
this.sorted=false;
this.sorted = false;
}
},
removePad: function(name)
{
if(!this.initiated) return;
removePad: function(name) {
if (!this.initiated) return;
var index = this.list.indexOf(name);
if(index>-1){
this.list.splice(index,1);
this.sorted=false;
if (index > -1) {
this.list.splice(index, 1);
this.sorted = false;
}
}
};
//initialises the allknowing data structure
// initialises the all-knowing data structure
/**
* An array of padId transformations. These represent changes in pad name policy over
@ -121,54 +127,52 @@ var padIdTransforms = [
*/
exports.getPad = function(id, text, callback)
{
//check if this is a valid padId
if(!exports.isValidPadId(id))
{
callback(new customError(id + " is not a valid padId","apierror"));
// check if this is a valid padId
if (!exports.isValidPadId(id)) {
callback(new customError(id + " is not a valid padId", "apierror"));
return;
}
//make text an optional parameter
if(typeof text == "function")
{
// make text an optional parameter
if (typeof text == "function") {
callback = text;
text = null;
}
//check if this is a valid text
if(text != null)
{
//check if text is a string
if(typeof text != "string")
{
callback(new customError("text is not a string","apierror"));
// check if this is a valid text
if (text != null) {
// check if text is a string
if (typeof text != "string") {
callback(new customError("text is not a string", "apierror"));
return;
}
//check if text is less than 100k chars
if(text.length > 100000)
{
callback(new customError("text must be less than 100k chars","apierror"));
// check if text is less than 100k chars
if (text.length > 100000) {
callback(new customError("text must be less than 100k chars", "apierror"));
return;
}
}
var pad = globalPads.get(id);
//return pad if its already loaded
if(pad != null)
{
// return pad if it's already loaded
if (pad != null) {
callback(null, pad);
return;
}
//try to load pad
// try to load pad
pad = new Pad(id);
//initalize the pad
pad.init(text, function(err)
{
if(ERR(err, callback)) return;
// initalize the pad
pad.init(text, function(err) {
if (ERR(err, callback)) return;
globalPads.set(id, pad);
padList.addPad(id);
callback(null, pad);
@ -182,49 +186,48 @@ exports.listAllPads = function(cb)
});
}
//checks if a pad exists
// checks if a pad exists
exports.doesPadExists = function(padId, callback)
{
db.get("pad:"+padId, function(err, value)
{
if(ERR(err, callback)) return;
if(value != null && value.atext){
db.get("pad:" + padId, function(err, value) {
if (ERR(err, callback)) return;
if (value != null && value.atext) {
callback(null, true);
}
else
{
} else {
callback(null, false);
}
});
}
//returns a sanitized padId, respecting legacy pad id formats
// returns a sanitized padId, respecting legacy pad id formats
exports.sanitizePadId = function(padId, callback) {
var transform_index = arguments[2] || 0;
//we're out of possible transformations, so just return it
if(transform_index >= padIdTransforms.length)
{
// we're out of possible transformations, so just return it
if (transform_index >= padIdTransforms.length) {
callback(padId);
return;
}
//check if padId exists
exports.doesPadExists(padId, function(junk, exists)
{
if(exists)
{
// check if padId exists
exports.doesPadExists(padId, function(junk, exists) {
if (exists) {
callback(padId);
return;
}
//get the next transformation *that's different*
// get the next transformation *that's different*
var transformedPadId = padId;
while(transformedPadId == padId && transform_index < padIdTransforms.length)
{
while(transformedPadId == padId && transform_index < padIdTransforms.length) {
transformedPadId = padId.replace(padIdTransforms[transform_index][0], padIdTransforms[transform_index][1]);
transform_index += 1;
}
//check the next transform
// check the next transform
exports.sanitizePadId(transformedPadId, callback, transform_index);
});
}
@ -237,13 +240,13 @@ exports.isValidPadId = function(padId)
/**
* Removes the pad from database and unloads it.
*/
exports.removePad = function(padId){
db.remove("pad:"+padId);
exports.removePad = function(padId) {
db.remove("pad:" + padId);
exports.unloadPad(padId);
padList.removePad(padId);
}
//removes a pad from the cache
// removes a pad from the cache
exports.unloadPad = function(padId)
{
globalPads.remove(padId);

View file

@ -33,39 +33,36 @@ exports.getReadOnlyId = function (padId, callback)
var readOnlyId;
async.waterfall([
//check if there is a pad2readonly entry
function(callback)
{
// check if there is a pad2readonly entry
function(callback) {
db.get("pad2readonly:" + padId, callback);
},
function(dbReadOnlyId, callback)
{
//there is no readOnly Entry in the database, let's create one
if(dbReadOnlyId == null)
{
function(dbReadOnlyId, callback) {
if (dbReadOnlyId == null) {
// there is no readOnly Entry in the database, let's create one
readOnlyId = "r." + randomString(16);
db.set("pad2readonly:" + padId, readOnlyId);
db.set("readonly2pad:" + readOnlyId, padId);
}
//there is a readOnly Entry in the database, let's take this one
else
{
} else {
// there is a readOnly Entry in the database, let's take this one
readOnlyId = dbReadOnlyId;
}
callback();
}
], function(err)
{
if(ERR(err, callback)) return;
//return the results
],
function(err) {
if (ERR(err, callback)) return;
// return the results
callback(null, readOnlyId);
})
}
/**
* returns a the padId for a read only id
* returns the padId for a read only id
* @param {String} readOnlyId read only id
*/
exports.getPadId = function(readOnlyId, callback)
@ -74,20 +71,21 @@ exports.getPadId = function(readOnlyId, callback)
}
/**
* returns a the padId and readonlyPadId in an object for any id
* returns the padId and readonlyPadId in an object for any id
* @param {String} padIdOrReadonlyPadId read only id or real pad id
*/
exports.getIds = function(id, callback) {
if (id.indexOf("r.") == 0)
if (id.indexOf("r.") == 0) {
exports.getPadId(id, function (err, value) {
if(ERR(err, callback)) return;
if (ERR(err, callback)) return;
callback(null, {
readOnlyPadId: id,
padId: value, // Might be null, if this is an unknown read-only id
readonly: true
});
});
else
} else {
exports.getReadOnlyId(id, function (err, value) {
callback(null, {
readOnlyPadId: value,
@ -95,4 +93,5 @@ exports.getIds = function(id, callback) {
readonly: false
});
});
}
}

View file

@ -18,7 +18,6 @@
* limitations under the License.
*/
var ERR = require("async-stacktrace");
var async = require("async");
var authorManager = require("./AuthorManager");
@ -37,56 +36,54 @@ var authLogger = log4js.getLogger("auth");
* @param password the password the user has given to access this pad, can be null
* @param callback will be called with (err, {accessStatus: grant|deny|wrongPassword|needPassword, authorID: a.xxxxxx})
*/
exports.checkAccess = function (padID, sessionCookie, token, password, callback)
exports.checkAccess = function(padID, sessionCookie, token, password, callback)
{
var statusObject;
if(!padID) {
if (!padID) {
callback(null, {accessStatus: "deny"});
return;
}
// allow plugins to deny access
var deniedByHook = hooks.callAll("onAccessCheck", {'padID': padID, 'password': password, 'token': token, 'sessionCookie': sessionCookie}).indexOf(false) > -1;
if(deniedByHook)
{
if (deniedByHook) {
callback(null, {accessStatus: "deny"});
return;
}
if (settings.requireSession) {
// a valid session is required (api-only mode)
if(settings.requireSession)
{
if (!sessionCookie) {
// without sessionCookie, access is denied
if(!sessionCookie)
{
callback(null, {accessStatus: "deny"});
return;
}
}
} else {
// a session is not required, so we'll check if it's a public pad
else
{
if (padID.indexOf("$") == -1) {
// it's not a group pad, means we can grant access
if(padID.indexOf("$") == -1)
{
//get author for this token
authorManager.getAuthor4Token(token, function(err, author)
{
if(ERR(err, callback)) return;
// get author for this token
authorManager.getAuthor4Token(token, function(err, author) {
if (ERR(err, callback)) return;
// assume user has access
statusObject = {accessStatus: "grant", authorID: author};
// user can't create pads
if(settings.editOnly)
{
// check if pad exists
padManager.doesPadExists(padID, function(err, exists)
{
if(ERR(err, callback)) return;
statusObject = { accessStatus: "grant", authorID: author };
if (settings.editOnly) {
// user can't create pads
// check if pad exists
padManager.doesPadExists(padID, function(err, exists) {
if (ERR(err, callback)) return;
if (!exists) {
// pad doesn't exist - user can't have access
if(!exists) statusObject.accessStatus = "deny";
statusObject.accessStatus = "deny";
}
// grant or deny access, with author of token
callback(null, statusObject);
});
@ -99,7 +96,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
callback(null, statusObject);
});
//don't continue
// don't continue
return;
}
}
@ -114,59 +111,55 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
var passwordStatus = password == null ? "notGiven" : "wrong"; // notGiven, correct, wrong
async.series([
//get basic informations from the database
function(callback)
{
// get basic informations from the database
function(callback) {
async.parallel([
//does pad exists
function(callback)
{
padManager.doesPadExists(padID, function(err, exists)
{
if(ERR(err, callback)) return;
// does pad exist
function(callback) {
padManager.doesPadExists(padID, function(err, exists) {
if (ERR(err, callback)) return;
padExists = exists;
callback();
});
},
//get information about all sessions contained in this cookie
function(callback)
{
if (!sessionCookie)
{
// get information about all sessions contained in this cookie
function(callback) {
if (!sessionCookie) {
callback();
return;
}
var sessionIDs = sessionCookie.split(',');
async.forEach(sessionIDs, function(sessionID, callback)
{
sessionManager.getSessionInfo(sessionID, function(err, sessionInfo)
{
//skip session if it doesn't exist
if(err && err.message == "sessionID does not exist")
{
async.forEach(sessionIDs, function(sessionID, callback) {
sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) {
// skip session if it doesn't exist
if (err && err.message == "sessionID does not exist") {
authLogger.debug("Auth failed: unknown session");
callback();
return;
}
if(ERR(err, callback)) return;
if (ERR(err, callback)) return;
var now = Math.floor(Date.now()/1000);
//is it for this group?
if(sessionInfo.groupID != groupID)
{
// is it for this group?
if (sessionInfo.groupID != groupID) {
authLogger.debug("Auth failed: wrong group");
callback();
return;
}
//is validUntil still ok?
if(sessionInfo.validUntil <= now)
{
// is validUntil still ok?
if (sessionInfo.validUntil <= now) {
authLogger.debug("Auth failed: validUntil");
callback();
return;
}
@ -178,152 +171,133 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
});
}, callback);
},
//get author for token
function(callback)
{
//get author for this token
authorManager.getAuthor4Token(token, function(err, author)
{
if(ERR(err, callback)) return;
// get author for token
function(callback) {
// get author for this token
authorManager.getAuthor4Token(token, function(err, author) {
if (ERR(err, callback)) return;
tokenAuthor = author;
callback();
});
}
], callback);
},
//get more informations of this pad, if avaiable
function(callback)
{
//skip this if the pad doesn't exists
if(padExists == false)
{
// get more informations of this pad, if avaiable
function(callback) {
// skip this if the pad doesn't exist
if (padExists == false) {
callback();
return;
}
padManager.getPad(padID, function(err, pad)
{
if(ERR(err, callback)) return;
padManager.getPad(padID, function(err, pad) {
if (ERR(err, callback)) return;
//is it a public pad?
// is it a public pad?
isPublic = pad.getPublicStatus();
//is it password protected?
// is it password protected?
isPasswordProtected = pad.isPasswordProtected();
//is password correct?
if(isPasswordProtected && password && pad.isCorrectPassword(password))
{
// is password correct?
if (isPasswordProtected && password && pad.isCorrectPassword(password)) {
passwordStatus = "correct";
}
callback();
});
},
function(callback)
{
//- a valid session for this group is avaible AND pad exists
if(validSession && padExists)
{
//- the pad is not password protected
if(!isPasswordProtected)
{
//--> grant access
statusObject = {accessStatus: "grant", authorID: sessionAuthor};
}
//- the setting to bypass password validation is set
else if(settings.sessionNoPassword)
{
//--> grant access
statusObject = {accessStatus: "grant", authorID: sessionAuthor};
}
//- the pad is password protected and password is correct
else if(isPasswordProtected && passwordStatus == "correct")
{
//--> grant access
statusObject = {accessStatus: "grant", authorID: sessionAuthor};
}
//- the pad is password protected but wrong password given
else if(isPasswordProtected && passwordStatus == "wrong")
{
//--> deny access, ask for new password and tell them that the password is wrong
statusObject = {accessStatus: "wrongPassword"};
}
//- the pad is password protected but no password given
else if(isPasswordProtected && passwordStatus == "notGiven")
{
//--> ask for password
statusObject = {accessStatus: "needPassword"};
}
else
{
function(callback) {
if (validSession && padExists) {
// - a valid session for this group is avaible AND pad exists
if (!isPasswordProtected) {
// - the pad is not password protected
// --> grant access
statusObject = { accessStatus: "grant", authorID: sessionAuthor };
} else if (settings.sessionNoPassword) {
// - the setting to bypass password validation is set
// --> grant access
statusObject = { accessStatus: "grant", authorID: sessionAuthor };
} else if (isPasswordProtected && passwordStatus == "correct") {
// - the pad is password protected and password is correct
// --> grant access
statusObject = { accessStatus: "grant", authorID: sessionAuthor };
} else if (isPasswordProtected && passwordStatus == "wrong") {
// - the pad is password protected but wrong password given
// --> deny access, ask for new password and tell them that the password is wrong
statusObject = { accessStatus: "wrongPassword" };
} else if (isPasswordProtected && passwordStatus == "notGiven") {
// - the pad is password protected but no password given
// --> ask for password
statusObject = { accessStatus: "needPassword" };
} else {
throw new Error("Ops, something wrong happend");
}
}
//- a valid session for this group avaible but pad doesn't exists
else if(validSession && !padExists)
{
//--> grant access
} else if (validSession && !padExists) {
// - a valid session for this group avaible but pad doesn't exist
// --> grant access
statusObject = {accessStatus: "grant", authorID: sessionAuthor};
//--> deny access if user isn't allowed to create the pad
if(settings.editOnly)
{
if (settings.editOnly) {
// --> deny access if user isn't allowed to create the pad
authLogger.debug("Auth failed: valid session & pad does not exist");
statusObject.accessStatus = "deny";
}
}
} else if (!validSession && padExists) {
// there is no valid session avaiable AND pad exists
else if(!validSession && padExists)
{
//-- its public and not password protected
if(isPublic && !isPasswordProtected)
{
//--> grant access, with author of token
// -- it's public and not password protected
if (isPublic && !isPasswordProtected) {
// --> grant access, with author of token
statusObject = {accessStatus: "grant", authorID: tokenAuthor};
}
//- its public and password protected and password is correct
else if(isPublic && isPasswordProtected && passwordStatus == "correct")
{
//--> grant access, with author of token
} else if (isPublic && isPasswordProtected && passwordStatus == "correct") {
// - it's public and password protected and password is correct
// --> grant access, with author of token
statusObject = {accessStatus: "grant", authorID: tokenAuthor};
}
//- its public and the pad is password protected but wrong password given
else if(isPublic && isPasswordProtected && passwordStatus == "wrong")
{
//--> deny access, ask for new password and tell them that the password is wrong
} else if (isPublic && isPasswordProtected && passwordStatus == "wrong") {
// - it's public and the pad is password protected but wrong password given
// --> deny access, ask for new password and tell them that the password is wrong
statusObject = {accessStatus: "wrongPassword"};
}
//- its public and the pad is password protected but no password given
else if(isPublic && isPasswordProtected && passwordStatus == "notGiven")
{
//--> ask for password
} else if (isPublic && isPasswordProtected && passwordStatus == "notGiven") {
// - it's public and the pad is password protected but no password given
// --> ask for password
statusObject = {accessStatus: "needPassword"};
}
//- its not public
else if(!isPublic)
{
} else if (!isPublic) {
// - it's not public
authLogger.debug("Auth failed: invalid session & pad is not public");
//--> deny access
// --> deny access
statusObject = {accessStatus: "deny"};
}
else
{
} else {
throw new Error("Ops, something wrong happend");
}
}
// there is no valid session avaiable AND pad doesn't exists
else
{
} else {
// there is no valid session avaiable AND pad doesn't exist
authLogger.debug("Auth failed: invalid session & pad does not exist");
//--> deny access
// --> deny access
statusObject = {accessStatus: "deny"};
}
callback();
}
], function(err)
{
if(ERR(err, callback)) return;
],
function(err) {
if (ERR(err, callback)) return;
callback(null, statusObject);
});
};

View file

@ -18,7 +18,6 @@
* limitations under the License.
*/
var ERR = require("async-stacktrace");
var customError = require("../utils/customError");
var randomString = require("../utils/randomstring");
@ -45,127 +44,119 @@ exports.createSession = function(groupID, authorID, validUntil, callback)
var sessionID;
async.series([
//check if group exists
// check if the group exists
function(callback)
{
groupMangager.doesGroupExist(groupID, function(err, exists)
{
if(ERR(err, callback)) return;
//group does not exist
if(exists == false)
{
callback(new customError("groupID does not exist","apierror"));
}
//everything is fine, continue
else
{
// group does not exist
if (exists == false) {
callback(new customError("groupID does not exist", "apierror"));
} else {
// everything is fine, continue
callback();
}
});
},
//check if author exists
// check if the author exists
function(callback)
{
authorMangager.doesAuthorExists(authorID, function(err, exists)
{
if(ERR(err, callback)) return;
//author does not exist
if(exists == false)
{
callback(new customError("authorID does not exist","apierror"));
}
//everything is fine, continue
else
{
if (exists == false) {
// author does not exist
callback(new customError("authorID does not exist", "apierror"));
} else {
// everything is fine, continue
callback();
}
});
},
//check validUntil and create the session db entry
// check validUntil and create the session db entry
function(callback)
{
//check if rev is a number
if(typeof validUntil != "number")
// check if rev is a number
if (typeof validUntil != "number")
{
//try to parse the number
if(isNaN(parseInt(validUntil)))
{
callback(new customError("validUntil is not a number","apierror"));
// try to parse the number
if (isNaN(parseInt(validUntil))) {
callback(new customError("validUntil is not a number", "apierror"));
return;
}
validUntil = parseInt(validUntil);
}
//ensure this is not a negativ number
if(validUntil < 0)
{
callback(new customError("validUntil is a negativ number","apierror"));
// ensure this is not a negative number
if (validUntil < 0) {
callback(new customError("validUntil is a negativ number", "apierror"));
return;
}
//ensure this is not a float value
if(!is_int(validUntil))
{
callback(new customError("validUntil is a float value","apierror"));
// ensure this is not a float value
if (!is_int(validUntil)) {
callback(new customError("validUntil is a float value", "apierror"));
return;
}
//check if validUntil is in the future
if(Math.floor(Date.now()/1000) > validUntil)
{
callback(new customError("validUntil is in the past","apierror"));
// check if validUntil is in the future
if (Math.floor(Date.now()/1000) > validUntil) {
callback(new customError("validUntil is in the past", "apierror"));
return;
}
//generate sessionID
// generate sessionID
sessionID = "s." + randomString(16);
//set the session into the database
// set the session into the database
db.set("session:" + sessionID, {"groupID": groupID, "authorID": authorID, "validUntil": validUntil});
callback();
},
//set the group2sessions entry
// set the group2sessions entry
function(callback)
{
//get the entry
// get the entry
db.get("group2sessions:" + groupID, function(err, group2sessions)
{
if(ERR(err, callback)) return;
//the entry doesn't exist so far, let's create it
if(group2sessions == null || group2sessions.sessionIDs == null)
{
if (group2sessions == null || group2sessions.sessionIDs == null) {
// the entry doesn't exist so far, let's create it
group2sessions = {sessionIDs : {}};
}
//add the entry for this session
// add the entry for this session
group2sessions.sessionIDs[sessionID] = 1;
//save the new element back
// save the new element back
db.set("group2sessions:" + groupID, group2sessions);
callback();
});
},
//set the author2sessions entry
// set the author2sessions entry
function(callback)
{
//get the entry
// get the entry
db.get("author2sessions:" + authorID, function(err, author2sessions)
{
if(ERR(err, callback)) return;
//the entry doesn't exist so far, let's create it
if(author2sessions == null || author2sessions.sessionIDs == null)
{
if (author2sessions == null || author2sessions.sessionIDs == null) {
// the entry doesn't exist so far, let's create it
author2sessions = {sessionIDs : {}};
}
//add the entry for this session
// add the entry for this session
author2sessions.sessionIDs[sessionID] = 1;
//save the new element back
@ -178,26 +169,23 @@ exports.createSession = function(groupID, authorID, validUntil, callback)
{
if(ERR(err, callback)) return;
//return error and sessionID
// return error and sessionID
callback(null, {sessionID: sessionID});
})
}
exports.getSessionInfo = function(sessionID, callback)
{
//check if the database entry of this session exists
// check if the database entry of this session exists
db.get("session:" + sessionID, function (err, session)
{
if(ERR(err, callback)) return;
//session does not exists
if(session == null)
{
callback(new customError("sessionID does not exist","apierror"))
}
//everything is fine, return the sessioninfos
else
{
if (session == null) {
// session does not exist
callback(new customError("sessionID does not exist", "apierror"))
} else {
// everything is fine, return the sessioninfos
callback(null, session);
}
});
@ -214,19 +202,16 @@ exports.deleteSession = function(sessionID, callback)
async.series([
function(callback)
{
//get the session entry
// get the session entry
db.get("session:" + sessionID, function (err, session)
{
if(ERR(err, callback)) return;
//session does not exists
if(session == null)
{
callback(new customError("sessionID does not exist","apierror"))
}
//everything is fine, return the sessioninfos
else
{
if (session == null) {
// session does not exist
callback(new customError("sessionID does not exist", "apierror"))
} else {
// everything is fine, use the sessioninfos
authorID = session.authorID;
groupID = session.groupID;
@ -234,7 +219,8 @@ exports.deleteSession = function(sessionID, callback)
}
});
},
//get the group2sessions entry
// get the group2sessions entry
function(callback)
{
db.get("group2sessions:" + groupID, function (err, _group2sessions)
@ -244,7 +230,8 @@ exports.deleteSession = function(sessionID, callback)
callback();
});
},
//get the author2sessions entry
// get the author2sessions entry
function(callback)
{
db.get("author2sessions:" + authorID, function (err, _author2sessions)
@ -254,19 +241,20 @@ exports.deleteSession = function(sessionID, callback)
callback();
});
},
//remove the values from the database
// remove the values from the database
function(callback)
{
//remove the session
db.remove("session:" + sessionID);
//remove session from group2sessions
// remove session from group2sessions
if(group2sessions != null) { // Maybe the group was already deleted
delete group2sessions.sessionIDs[sessionID];
db.set("group2sessions:" + groupID, group2sessions);
}
//remove session from author2sessions
// remove session from author2sessions
if(author2sessions != null) { // Maybe the author was already deleted
delete author2sessions.sessionIDs[sessionID];
db.set("author2sessions:" + authorID, author2sessions);
@ -287,14 +275,11 @@ exports.listSessionsOfGroup = function(groupID, callback)
{
if(ERR(err, callback)) return;
//group does not exist
if(exists == false)
{
callback(new customError("groupID does not exist","apierror"));
}
//everything is fine, continue
else
{
if (exists == false) {
// group does not exist
callback(new customError("groupID does not exist", "apierror"));
} else {
// everything is fine, continue
listSessionsWithDBKey("group2sessions:" + groupID, callback);
}
});
@ -306,20 +291,17 @@ exports.listSessionsOfAuthor = function(authorID, callback)
{
if(ERR(err, callback)) return;
//group does not exist
if(exists == false)
{
callback(new customError("authorID does not exist","apierror"));
}
//everything is fine, continue
else
{
if (exists == false) {
// group does not exist
callback(new customError("authorID does not exist", "apierror"));
} else {
// everything is fine, continue
listSessionsWithDBKey("author2sessions:" + authorID, callback);
}
});
}
//this function is basicly the code listSessionsOfAuthor and listSessionsOfGroup has in common
// this function is basically the code listSessionsOfAuthor and listSessionsOfGroup has in common
function listSessionsWithDBKey (dbkey, callback)
{
var sessions;
@ -327,7 +309,7 @@ function listSessionsWithDBKey (dbkey, callback)
async.series([
function(callback)
{
//get the group2sessions entry
// get the group2sessions entry
db.get(dbkey, function(err, sessionObject)
{
if(ERR(err, callback)) return;
@ -335,26 +317,24 @@ function listSessionsWithDBKey (dbkey, callback)
callback();
});
},
function(callback)
{
//collect all sessionIDs in an arrary
// collect all sessionIDs in an arrary
var sessionIDs = [];
for (var i in sessions)
{
sessionIDs.push(i);
}
//foreach trough the sessions and get the sessioninfos
// iterate through the sessions and get the sessioninfos
async.forEach(sessionIDs, function(sessionID, callback)
{
exports.getSessionInfo(sessionID, function(err, sessionInfo)
{
if (err == "apierror: sessionID does not exist")
{
if (err == "apierror: sessionID does not exist") {
console.warn(`Found bad session ${sessionID} in ${dbkey}`);
}
else if(ERR(err, callback))
{
} else if(ERR(err, callback)) {
return;
}
@ -370,8 +350,8 @@ function listSessionsWithDBKey (dbkey, callback)
});
}
//checks if a number is an int
// checks if a number is an int
function is_int(value)
{
return (parseFloat(value) == parseInt(value)) && !isNaN(value)
return (parseFloat(value) == parseInt(value)) && !isNaN(value);
}

View file

@ -1,4 +1,4 @@
/*
/*
* Stores session data in the database
* Source; https://github.com/edy-b/SciFlowWriter/blob/develop/available_plugins/ep_sciflowwriter/db/DirtyStore.js
* This is not used for authors that are created via the API at current
@ -13,11 +13,12 @@ var SessionStore = module.exports = function SessionStore() {};
SessionStore.prototype.__proto__ = Store.prototype;
SessionStore.prototype.get = function(sid, fn){
SessionStore.prototype.get = function(sid, fn) {
messageLogger.debug('GET ' + sid);
var self = this;
db.get("sessionstorage:" + sid, function (err, sess)
{
db.get("sessionstorage:" + sid, function(err, sess) {
if (sess) {
sess.cookie.expires = 'string' == typeof sess.cookie.expires ? new Date(sess.cookie.expires) : sess.cookie.expires;
if (!sess.cookie.expires || new Date() < sess.cookie.expires) {
@ -31,26 +32,30 @@ SessionStore.prototype.get = function(sid, fn){
});
};
SessionStore.prototype.set = function(sid, sess, fn){
SessionStore.prototype.set = function(sid, sess, fn) {
messageLogger.debug('SET ' + sid);
db.set("sessionstorage:" + sid, sess);
process.nextTick(function(){
if(fn) fn();
process.nextTick(function() {
if (fn) fn();
});
};
SessionStore.prototype.destroy = function(sid, fn){
SessionStore.prototype.destroy = function(sid, fn) {
messageLogger.debug('DESTROY ' + sid);
db.remove("sessionstorage:" + sid);
process.nextTick(function(){
if(fn) fn();
process.nextTick(function() {
if (fn) fn();
});
};
SessionStore.prototype.all = function(fn){
SessionStore.prototype.all = function(fn) {
messageLogger.debug('ALL');
var sessions = [];
db.forEach(function(key, value){
db.forEach(function(key, value) {
if (key.substr(0,15) === "sessionstorage:") {
sessions.push(value);
}
@ -58,20 +63,23 @@ SessionStore.prototype.all = function(fn){
fn(null, sessions);
};
SessionStore.prototype.clear = function(fn){
SessionStore.prototype.clear = function(fn) {
messageLogger.debug('CLEAR');
db.forEach(function(key, value){
db.forEach(function(key, value) {
if (key.substr(0,15) === "sessionstorage:") {
db.db.remove("session:" + key);
}
});
if(fn) fn();
if (fn) fn();
};
SessionStore.prototype.length = function(fn){
SessionStore.prototype.length = function(fn) {
messageLogger.debug('LENGTH');
var i = 0;
db.forEach(function(key, value){
db.forEach(function(key, value) {
if (key.substr(0,15) === "sessionstorage:") {
i++;
}

View file

@ -32,19 +32,17 @@ var apiHandlerLogger = log4js.getLogger('APIHandler');
//ensure we have an apikey
var apikey = null;
var apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || "./APIKEY.txt");
try
{
try {
apikey = fs.readFileSync(apikeyFilename,"utf8");
apiHandlerLogger.info(`Api key file read from: "${apikeyFilename}"`);
}
catch(e)
{
} catch(e) {
apiHandlerLogger.info(`Api key file "${apikeyFilename}" not found. Creating with random contents.`);
apikey = randomString(32);
fs.writeFileSync(apikeyFilename,apikey,"utf8");
}
//a list of all functions
// a list of all functions
var version = {};
version["1"] = Object.assign({},
@ -156,106 +154,92 @@ exports.handle = function(apiVersion, functionName, fields, req, res)
{
//check if this is a valid apiversion
var isKnownApiVersion = false;
for(var knownApiVersion in version)
{
if(knownApiVersion == apiVersion)
{
for (var knownApiVersion in version) {
if (knownApiVersion == apiVersion) {
isKnownApiVersion = true;
break;
}
}
//say goodbye if this is an unknown API version
if(!isKnownApiVersion)
{
// say goodbye if this is an unknown API version
if (!isKnownApiVersion) {
res.statusCode = 404;
res.send({code: 3, message: "no such api version", data: null});
return;
}
//check if this is a valid function name
// check if this is a valid function name
var isKnownFunctionname = false;
for(var knownFunctionname in version[apiVersion])
{
if(knownFunctionname == functionName)
{
for (var knownFunctionname in version[apiVersion]) {
if (knownFunctionname == functionName) {
isKnownFunctionname = true;
break;
}
}
//say goodbye if this is a unknown function
if(!isKnownFunctionname)
{
// say goodbye if this is an unknown function
if (!isKnownFunctionname) {
res.send({code: 3, message: "no such function", data: null});
return;
}
//check the api key!
// check the api key!
fields["apikey"] = fields["apikey"] || fields["api_key"];
if(fields["apikey"] != apikey.trim())
{
if (fields["apikey"] != apikey.trim()) {
res.statusCode = 401;
res.send({code: 4, message: "no or wrong API Key", data: null});
return;
}
//sanitize any pad id's before continuing
if(fields["padID"])
{
padManager.sanitizePadId(fields["padID"], function(padId)
{
// sanitize any padIDs before continuing
if (fields["padID"]) {
padManager.sanitizePadId(fields["padID"], function(padId) {
fields["padID"] = padId;
callAPI(apiVersion, functionName, fields, req, res);
});
}
else if(fields["padName"])
{
padManager.sanitizePadId(fields["padName"], function(padId)
{
} else if (fields["padName"]) {
padManager.sanitizePadId(fields["padName"], function(padId) {
fields["padName"] = padId;
callAPI(apiVersion, functionName, fields, req, res);
});
}
else
{
} else {
callAPI(apiVersion, functionName, fields, req, res);
}
}
//calls the api function
// calls the api function
function callAPI(apiVersion, functionName, fields, req, res)
{
//put the function parameters in an array
// put the function parameters in an array
var functionParams = version[apiVersion][functionName].map(function (field) {
return fields[field]
})
});
//add a callback function to handle the response
functionParams.push(function(err, data)
{
// no error happend, everything is fine
if(err == null)
{
if(!data)
// add a callback function to handle the response
functionParams.push(function(err, data) {
if (err == null) {
// no error happened, everything is fine
if (!data) {
data = null;
}
res.send({code: 0, message: "ok", data: data});
}
} else if (err.name == "apierror") {
// parameters were wrong and the api stopped execution, pass the error
else if(err.name == "apierror")
{
res.send({code: 1, message: err.message, data: null});
}
//an unknown error happend
else
{
} else {
// an unknown error happened
res.send({code: 2, message: "internal error", data: null});
ERR(err);
}
});
//call the api function
// call the api function
api[functionName].apply(this, functionParams);
}

View file

@ -32,13 +32,15 @@ var TidyHtml = require('../utils/TidyHtml');
var convertor = null;
//load abiword only if its enabled
if(settings.abiword != null)
// load abiword only if it is enabled
if (settings.abiword != null) {
convertor = require("../utils/Abiword");
}
// Use LibreOffice if an executable has been defined in the settings
if(settings.soffice != null)
if (settings.soffice != null) {
convertor = require("../utils/LibreOffice");
}
const tempDirectory = os.tmpdir();
@ -53,62 +55,55 @@ exports.doExport = function(req, res, padId, type)
hooks.aCallFirst("exportFileName", padId,
function(err, hookFileName){
// if fileName is set then set it to the padId, note that fileName is returned as an array.
if(hookFileName.length) fileName = hookFileName;
if (hookFileName.length) {
fileName = hookFileName;
}
//tell the browser that this is a downloadable file
// tell the browser that this is a downloadable file
res.attachment(fileName + "." + type);
//if this is a plain text export, we can do this directly
// if this is a plain text export, we can do this directly
// We have to over engineer this because tabs are stored as attributes and not plain text
if(type == "etherpad"){
exportEtherpad.getPadRaw(padId, function(err, pad){
if(!err){
if (type == "etherpad") {
exportEtherpad.getPadRaw(padId, function(err, pad) {
if (!err) {
res.send(pad);
// return;
}
});
}
else if(type == "txt")
{
exporttxt.getPadTXTDocument(padId, req.params.rev, function(err, txt)
{
if(!err) {
} else if (type == "txt") {
exporttxt.getPadTXTDocument(padId, req.params.rev, function(err, txt) {
if (!err) {
res.send(txt);
}
});
}
else
{
} else {
var html;
var randNum;
var srcFile, destFile;
async.series([
//render the html document
function(callback)
{
exporthtml.getPadHTMLDocument(padId, req.params.rev, function(err, _html)
{
if(ERR(err, callback)) return;
// render the html document
function(callback) {
exporthtml.getPadHTMLDocument(padId, req.params.rev, function(err, _html) {
if (ERR(err, callback)) return;
html = _html;
callback();
});
},
//decide what to do with the html export
function(callback)
{
//if this is a html export, we can send this from here directly
if(type == "html")
{
// decide what to do with the html export
function(callback) {
// if this is a html export, we can send this from here directly
if (type == "html") {
// do any final changes the plugin might want to make
hooks.aCallFirst("exportHTMLSend", html, function(err, newHTML){
if(newHTML.length) html = newHTML;
hooks.aCallFirst("exportHTMLSend", html, function(err, newHTML) {
if (newHTML.length) html = newHTML;
res.send(html);
callback("stop");
});
}
else //write the html export to a file
{
} else {
// write the html export to a file
randNum = Math.floor(Math.random()*0xFFFFFFFF);
srcFile = tempDirectory + "/etherpad_export_" + randNum + ".html";
fs.writeFile(srcFile, html, callback);
@ -116,64 +111,56 @@ exports.doExport = function(req, res, padId, type)
},
// Tidy up the exported HTML
function(callback)
{
//ensure html can be collected by the garbage collector
function(callback) {
// ensure html can be collected by the garbage collector
html = null;
TidyHtml.tidy(srcFile, callback);
},
//send the convert job to the convertor (abiword, libreoffice, ..)
function(callback)
{
// send the convert job to the convertor (abiword, libreoffice, ..)
function(callback) {
destFile = tempDirectory + "/etherpad_export_" + randNum + "." + type;
// Allow plugins to overwrite the convert in export process
hooks.aCallAll("exportConvert", {srcFile: srcFile, destFile: destFile, req: req, res: res}, function(err, result){
if(!err && result.length > 0){
hooks.aCallAll("exportConvert", { srcFile: srcFile, destFile: destFile, req: req, res: res }, function(err, result) {
if (!err && result.length > 0) {
// console.log("export handled by plugin", destFile);
handledByPlugin = true;
callback();
}else{
} else {
convertor.convertFile(srcFile, destFile, type, callback);
}
});
},
//send the file
function(callback)
{
// send the file
function(callback) {
res.sendFile(destFile, null, callback);
},
//clean up temporary files
function(callback)
{
// clean up temporary files
function(callback) {
async.parallel([
function(callback)
{
function(callback) {
fs.unlink(srcFile, callback);
},
function(callback)
{
//100ms delay to accomidate for slow windows fs
if(os.type().indexOf("Windows") > -1)
{
setTimeout(function()
{
function(callback) {
// 100ms delay to accommodate for slow windows fs
if (os.type().indexOf("Windows") > -1) {
setTimeout(function() {
fs.unlink(destFile, callback);
}, 100);
}
else
{
} else {
fs.unlink(destFile, callback);
}
}
], callback);
}
], function(err)
{
if(err && err != "stop") ERR(err);
],
function(err) {
if (err && err != "stop") ERR(err);
})
}
}

View file

@ -37,12 +37,13 @@ var ERR = require("async-stacktrace")
var convertor = null;
var exportExtension = "htm";
//load abiword only if its enabled and if soffice is disabled
if(settings.abiword != null && settings.soffice === null)
// load abiword only if it is enabled and if soffice is disabled
if (settings.abiword != null && settings.soffice === null) {
convertor = require("../utils/Abiword");
}
//load soffice only if its enabled
if(settings.soffice != null) {
// load soffice only if it is enabled
if (settings.soffice != null) {
convertor = require("../utils/LibreOffice");
exportExtension = "html";
}
@ -56,9 +57,9 @@ exports.doImport = function(req, res, padId)
{
var apiLogger = log4js.getLogger("ImportHandler");
//pipe to a file
//convert file to html via abiword or soffice
//set html in the pad
// pipe to a file
// convert file to html via abiword or soffice
// set html in the pad
var srcFile, destFile
, pad
@ -73,64 +74,69 @@ exports.doImport = function(req, res, padId)
useConvertor = (convertor != null);
async.series([
//save the uploaded file to /tmp
// save the uploaded file to /tmp
function(callback) {
var form = new formidable.IncomingForm();
form.keepExtensions = true;
form.uploadDir = tmpDirectory;
form.parse(req, function(err, fields, files) {
//the upload failed, stop at this point
if(err || files.file === undefined) {
if(err) console.warn("Uploading Error: " + err.stack);
if (err || files.file === undefined) {
// the upload failed, stop at this point
if (err) {
console.warn("Uploading Error: " + err.stack);
}
callback("uploadFailed");
return;
}
//everything ok, continue
//save the path of the uploaded file
// everything ok, continue
// save the path of the uploaded file
srcFile = files.file.path;
callback();
});
},
//ensure this is a file ending we know, else we change the file ending to .txt
//this allows us to accept source code files like .c or .java
// ensure this is a file ending we know, else we change the file ending to .txt
// this allows us to accept source code files like .c or .java
function(callback) {
var fileEnding = path.extname(srcFile).toLowerCase()
, knownFileEndings = [".txt", ".doc", ".docx", ".pdf", ".odt", ".html", ".htm", ".etherpad", ".rtf"]
, fileEndingKnown = (knownFileEndings.indexOf(fileEnding) > -1);
//if the file ending is known, continue as normal
if(fileEndingKnown) {
// if the file ending is known, continue as normal
if (fileEndingKnown) {
callback();
return;
}
//we need to rename this file with a .txt ending
if(settings.allowUnknownFileEnds === true){
// we need to rename this file with a .txt ending
if (settings.allowUnknownFileEnds === true) {
var oldSrcFile = srcFile;
srcFile = path.join(path.dirname(srcFile),path.basename(srcFile, fileEnding)+".txt");
srcFile = path.join(path.dirname(srcFile), path.basename(srcFile, fileEnding) + ".txt");
fs.rename(oldSrcFile, srcFile, callback);
}else{
} else {
console.warn("Not allowing unknown file type to be imported", fileEnding);
callback("uploadFailed");
}
},
function(callback){
function(callback) {
destFile = path.join(tmpDirectory, "etherpad_import_" + randNum + "." + exportExtension);
// Logic for allowing external Import Plugins
hooks.aCallAll("import", {srcFile: srcFile, destFile: destFile}, function(err, result){
if(ERR(err, callback)) return callback();
if(result.length > 0){ // This feels hacky and wrong..
hooks.aCallAll("import", { srcFile: srcFile, destFile: destFile }, function(err, result) {
if (ERR(err, callback)) return callback();
if (result.length > 0) { // This feels hacky and wrong..
importHandledByPlugin = true;
}
callback();
});
},
function(callback) {
var fileEnding = path.extname(srcFile).toLowerCase()
var fileIsNotEtherpad = (fileEnding !== ".etherpad");
@ -141,23 +147,24 @@ exports.doImport = function(req, res, padId)
return;
}
// we do this here so we can see if the pad has quit ea few edits
padManager.getPad(padId, function(err, _pad){
// we do this here so we can see if the pad has quite a few edits
padManager.getPad(padId, function(err, _pad) {
var headCount = _pad.head;
if(headCount >= 10){
apiLogger.warn("Direct database Import attempt of a pad that already has content, we wont be doing this")
if (headCount >= 10) {
apiLogger.warn("Direct database Import attempt of a pad that already has content, we wont be doing this");
return callback("padHasData");
}
fs.readFile(srcFile, "utf8", function(err, _text){
fs.readFile(srcFile, "utf8", function(err, _text) {
directDatabaseAccess = true;
importEtherpad.setPadRaw(padId, _text, function(err){
importEtherpad.setPadRaw(padId, _text, function(err) {
callback();
});
});
});
},
//convert file to html
// convert file to html if necessary
function(callback) {
if (importHandledByPlugin || directDatabaseAccess) {
callback();
@ -168,7 +175,9 @@ exports.doImport = function(req, res, padId)
var fileEnding = path.extname(srcFile).toLowerCase();
var fileIsHTML = (fileEnding === ".html" || fileEnding === ".htm");
var fileIsTXT = (fileEnding === ".txt");
if (fileIsTXT) useConvertor = false; // Don't use convertor for text files
// See https://github.com/ether/etherpad-lite/issues/2572
if (fileIsHTML || (useConvertor === false)) {
// if no convertor only rename
@ -178,8 +187,8 @@ exports.doImport = function(req, res, padId)
}
convertor.convertFile(srcFile, destFile, exportExtension, function(err) {
//catch convert errors
if(err) {
// catch convert errors
if (err) {
console.warn("Converting Error:", err);
return callback("convertFailed");
}
@ -217,16 +226,16 @@ exports.doImport = function(req, res, padId)
});
},
//get the pad object
// get the pad object
function(callback) {
padManager.getPad(padId, function(err, _pad){
if(ERR(err, callback)) return;
padManager.getPad(padId, function(err, _pad) {
if (ERR(err, callback)) return;
pad = _pad;
callback();
});
},
//read the text
// read the text
function(callback) {
if (directDatabaseAccess) {
callback();
@ -234,16 +243,16 @@ exports.doImport = function(req, res, padId)
return;
}
fs.readFile(destFile, "utf8", function(err, _text){
if(ERR(err, callback)) return;
fs.readFile(destFile, "utf8", function(err, _text) {
if (ERR(err, callback)) return;
text = _text;
// Title needs to be stripped out else it appends it to the pad..
text = text.replace("<title>", "<!-- <title>");
text = text.replace("</title>","</title>-->");
//node on windows has a delay on releasing of the file lock.
//We add a 100ms delay to work around this
if(os.type().indexOf("Windows") > -1){
// node on windows has a delay on releasing of the file lock.
// We add a 100ms delay to work around this
if (os.type().indexOf("Windows") > -1) {
setTimeout(function() {callback();}, 100);
} else {
callback();
@ -251,26 +260,29 @@ exports.doImport = function(req, res, padId)
});
},
//change text of the pad and broadcast the changeset
// change text of the pad and broadcast the changeset
function(callback) {
if(!directDatabaseAccess){
if (!directDatabaseAccess) {
var fileEnding = path.extname(srcFile).toLowerCase();
if (importHandledByPlugin || useConvertor || fileEnding == ".htm" || fileEnding == ".html") {
importHtml.setPadHTML(pad, text, function(e){
if(e) apiLogger.warn("Error importing, possibly caused by malformed HTML");
if (e) {
apiLogger.warn("Error importing, possibly caused by malformed HTML");
}
});
} else {
pad.setText(text);
}
}
// Load the Pad into memory then brodcast updates to all clients
// Load the Pad into memory then broadcast updates to all clients
padManager.unloadPad(padId);
padManager.getPad(padId, function(err, _pad){
padManager.getPad(padId, function(err, _pad) {
var pad = _pad;
padManager.unloadPad(padId);
// direct Database Access means a pad user should perform a switchToPad
// and not attempt to recieve updated pad data..
// and not attempt to receive updated pad data
if (directDatabaseAccess) {
callback();
@ -284,7 +296,7 @@ exports.doImport = function(req, res, padId)
},
//clean up temporary files
// clean up temporary files
function(callback) {
if (directDatabaseAccess) {
callback();
@ -309,16 +321,15 @@ exports.doImport = function(req, res, padId)
], function(err) {
var status = "ok";
//check for known errors and replace the status
if(err == "uploadFailed" || err == "convertFailed" || err == "padHasData")
{
// check for known errors and replace the status
if (err == "uploadFailed" || err == "convertFailed" || err == "padHasData") {
status = err;
err = null;
}
ERR(err);
//close the connection
// close the connection
res.send(
"<head> \
<script type='text/javascript' src='../../static/js/jquery.js'></script> \
@ -331,4 +342,3 @@ exports.doImport = function(req, res, padId)
);
});
}

File diff suppressed because it is too large Load diff

View file

@ -41,10 +41,10 @@ var socket;
*/
exports.addComponent = function(moduleName, module)
{
//save the component
// save the component
components[moduleName] = module;
//give the module the socket
// give the module the socket
module.setSocketIO(socket);
}
@ -52,57 +52,55 @@ exports.addComponent = function(moduleName, module)
* sets the socket.io and adds event functions for routing
*/
exports.setSocketIO = function(_socket) {
//save this socket internaly
// save this socket internaly
socket = _socket;
socket.sockets.on('connection', function(client)
{
// Broken: See http://stackoverflow.com/questions/4647348/send-message-to-specific-client-with-socket-io-and-node-js
// Fixed by having a persistant object, ideally this would actually be in the database layer
// TODO move to database layer
if(settings.trustProxy && client.handshake.headers['x-forwarded-for'] !== undefined){
if (settings.trustProxy && client.handshake.headers['x-forwarded-for'] !== undefined) {
remoteAddress[client.id] = client.handshake.headers['x-forwarded-for'];
}
else{
} else {
remoteAddress[client.id] = client.handshake.address;
}
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 = function(message) {
messageLogger.debug("to " + client.id + ": " + stringifyWithoutPassword(message));
client._send(message);
}
//tell all components about this connect
for(var i in components) {
// tell all components about this connect
for (var i in components) {
components[i].handleConnect(client);
}
client.on('message', function(message)
{
if(message.protocolVersion && message.protocolVersion != 2) {
client.on('message', function(message) {
if (message.protocolVersion && message.protocolVersion != 2) {
messageLogger.warn("Protocolversion header is not correct:" + stringifyWithoutPassword(message));
return;
}
//client is authorized, everything ok
if(clientAuthorized) {
if (clientAuthorized) {
// client is authorized, everything ok
handleMessage(client, message);
} else { //try to authorize the client
if(message.padId !== undefined && message.sessionID !== undefined && message.token !== undefined && message.password !== undefined) {
} else {
// try to authorize the client
if (message.padId !== undefined && message.sessionID !== undefined && message.token !== undefined && message.password !== undefined) {
var checkAccessCallback = function(err, statusObject) {
ERR(err);
//access was granted, mark the client as authorized and handle the message
if(statusObject.accessStatus == "grant") {
if (statusObject.accessStatus == "grant") {
// access was granted, mark the client as authorized and handle the message
clientAuthorized = true;
handleMessage(client, message);
}
//no access, send the client a message that tell him why
else {
} else {
// no access, send the client a message that tells him why
messageLogger.warn("Authentication try failed:" + stringifyWithoutPassword(message));
client.json.send({accessStatus: statusObject.accessStatus});
}
@ -110,36 +108,34 @@ exports.setSocketIO = function(_socket) {
if (message.padId.indexOf("r.") === 0) {
readOnlyManager.getPadId(message.padId, function(err, value) {
ERR(err);
securityManager.checkAccess (value, message.sessionID, message.token, message.password, checkAccessCallback);
securityManager.checkAccess(value, message.sessionID, message.token, message.password, checkAccessCallback);
});
} else {
//this message has everything to try an authorization
// this message has everything to try an authorization
securityManager.checkAccess (message.padId, message.sessionID, message.token, message.password, checkAccessCallback);
}
} else { //drop message
} else {
// drop message
messageLogger.warn("Dropped message cause of bad permissions:" + stringifyWithoutPassword(message));
}
}
});
client.on('disconnect', function()
{
//tell all components about this disconnect
for(var i in components)
{
client.on('disconnect', function() {
// tell all components about this disconnect
for (var i in components) {
components[i].handleDisconnect(client);
}
});
});
}
//try to handle the message of this client
// try to handle the message of this client
function handleMessage(client, message)
{
if(message.component && components[message.component]) {
//check if component is registered in the components array
if(components[message.component]) {
if (message.component && components[message.component]) {
// check if component is registered in the components array
if (components[message.component]) {
messageLogger.debug("from " + client.id + ": " + stringifyWithoutPassword(message));
components[message.component].handleMessage(client, message);
}
@ -148,18 +144,18 @@ function handleMessage(client, message)
}
}
//returns a stringified representation of a message, removes the password
//this ensures there are no passwords in the log
// returns a stringified representation of a message, removes the password
// this ensures there are no passwords in the log
function stringifyWithoutPassword(message)
{
var newMessage = {};
for(var i in message)
{
if(i == "password" && message[i] != null)
for (var i in message) {
if (i == "password" && message[i] != null) {
newMessage["password"] = "xxx";
else
newMessage[i]=message[i];
} else {
newMessage[i] = message[i];
}
}
return JSON.stringify(newMessage);

View file

@ -5,7 +5,7 @@ var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
var _ = require('underscore');
var semver = require('semver');
exports.expressCreateServer = function (hook_name, args, cb) {
exports.expressCreateServer = function(hook_name, args, cb) {
args.app.get('/admin/plugins', function(req, res) {
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
var render_args = {
@ -13,74 +13,81 @@ exports.expressCreateServer = function (hook_name, args, cb) {
search_results: {},
errors: [],
};
res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins.html", render_args) );
res.send(eejs.require("ep_etherpad-lite/templates/admin/plugins.html", render_args));
});
args.app.get('/admin/plugins/info', function(req, res) {
var gitCommit = settings.getGitCommit();
var epVersion = settings.getEpVersion();
res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html",
{
res.send(eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html", {
gitCommit: gitCommit,
epVersion: epVersion
})
);
}));
});
}
exports.socketio = function (hook_name, args, cb) {
exports.socketio = function(hook_name, args, cb) {
var io = args.io.of("/pluginfw/installer");
io.on('connection', function (socket) {
io.on('connection', function(socket) {
if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return;
socket.on("getInstalled", function (query) {
socket.on("getInstalled", function(query) {
// send currently installed plugins
var installed = Object.keys(plugins.plugins).map(function(plugin) {
return plugins.plugins[plugin].package
})
});
socket.emit("results:installed", {installed: installed});
});
socket.on("checkUpdates", function() {
// Check plugins for updates
installer.getAvailablePlugins(/*maxCacheAge:*/60*10, function(er, results) {
if(er) {
installer.getAvailablePlugins(/*maxCacheAge:*/ 60 * 10, function(er, results) {
if (er) {
console.warn(er);
socket.emit("results:updatable", {updatable: {}});
return;
}
var updatable = _(plugins.plugins).keys().filter(function(plugin) {
if(!results[plugin]) return false;
var latestVersion = results[plugin].version
var currentVersion = plugins.plugins[plugin].package.version
return semver.gt(latestVersion, currentVersion)
if (!results[plugin]) return false;
var latestVersion = results[plugin].version;
var currentVersion = plugins.plugins[plugin].package.version;
return semver.gt(latestVersion, currentVersion);
});
socket.emit("results:updatable", {updatable: updatable});
});
})
});
socket.on("getAvailable", function (query) {
installer.getAvailablePlugins(/*maxCacheAge:*/false, function (er, results) {
if(er) {
console.error(er)
results = {}
socket.on("getAvailable", function(query) {
installer.getAvailablePlugins(/*maxCacheAge:*/ false, function(er, results) {
if (er) {
console.error(er);
results = {};
}
socket.emit("results:available", results);
});
});
socket.on("search", function (query) {
installer.search(query.searchTerm, /*maxCacheAge:*/60*10, function (er, results) {
if(er) {
console.error(er)
results = {}
socket.on("search", function(query) {
installer.search(query.searchTerm, /*maxCacheAge:*/ 60 * 10, function(er, results) {
if (er) {
console.error(er);
results = {};
}
var res = Object.keys(results)
.map(function(pluginName) {
return results[pluginName]
return results[pluginName];
})
.filter(function(plugin) {
return !plugins.plugins[plugin.name]
return !plugins.plugins[plugin.name];
});
res = sortPluginList(res, query.sortBy, query.sortDir)
.slice(query.offset, query.offset+query.limit);
@ -88,16 +95,18 @@ exports.socketio = function (hook_name, args, cb) {
});
});
socket.on("install", function (plugin_name) {
installer.install(plugin_name, function (er) {
if(er) console.warn(er)
socket.on("install", function(plugin_name) {
installer.install(plugin_name, function(er) {
if (er) console.warn(er);
socket.emit("finished:install", {plugin: plugin_name, code: er? er.code : null, error: er? er.message : null});
});
});
socket.on("uninstall", function (plugin_name) {
installer.uninstall(plugin_name, function (er) {
if(er) console.warn(er)
socket.on("uninstall", function(plugin_name) {
installer.uninstall(plugin_name, function(er) {
if (er) console.warn(er);
socket.emit("finished:uninstall", {plugin: plugin_name, error: er? er.message : null});
});
});
@ -106,11 +115,15 @@ exports.socketio = function (hook_name, args, cb) {
function sortPluginList(plugins, property, /*ASC?*/dir) {
return plugins.sort(function(a, b) {
if (a[property] < b[property])
if (a[property] < b[property]) {
return dir? -1 : 1;
if (a[property] > b[property])
}
if (a[property] > b[property]) {
return dir? 1 : -1;
}
// a must be equal to b
return 0;
})
});
}

View file

@ -11,20 +11,23 @@ exports.gracefulShutdown = function(err) {
console.error(err);
}
//ensure there is only one graceful shutdown running
if(exports.onShutdown) return;
// ensure there is only one graceful shutdown running
if (exports.onShutdown) {
return;
}
exports.onShutdown = true;
console.log("graceful shutdown...");
//do the db shutdown
// do the db shutdown
db.db.doShutdown(function() {
console.log("db sucessfully closed.");
process.exit(0);
});
setTimeout(function(){
setTimeout(function() {
process.exit(1);
}, 3000);
}
@ -35,14 +38,14 @@ exports.expressCreateServer = function (hook_name, args, cb) {
exports.app = args.app;
// Handle errors
args.app.use(function(err, req, res, next){
args.app.use(function(err, req, res, next) {
// if an error occurs Connect will pass it down
// through these "error-handling" middleware
// allowing you to respond however you like
res.status(500).send({ error: 'Sorry, something bad happened!' });
console.error(err.stack? err.stack : err.toString());
stats.meter('http500').mark()
})
});
/*
* Connect graceful shutdown with sigint and uncaught exception

View file

@ -13,7 +13,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
return;
}
//if abiword is disabled, and this is a format we only support with abiword, output a message
// if abiword is disabled, and this is a format we only support with abiword, output a message
if (settings.exportAvailable() == "no" &&
["odt", "pdf", "doc"].indexOf(req.params.type) !== -1) {
res.send("This export is not enabled at this Etherpad instance. Set the path to Abiword or SOffice in settings.json to enable this feature");
@ -24,9 +24,8 @@ exports.expressCreateServer = function (hook_name, args, cb) {
hasPadAccess(req, res, function() {
console.log('req.params.pad', req.params.pad);
padManager.doesPadExists(req.params.pad, function(err, exists)
{
if(!exists) {
padManager.doesPadExists(req.params.pad, function(err, exists) {
if (!exists) {
return next();
}
@ -35,12 +34,11 @@ exports.expressCreateServer = function (hook_name, args, cb) {
});
});
//handle import requests
// handle import requests
args.app.post('/p/:pad/import', function(req, res, next) {
hasPadAccess(req, res, function() {
padManager.doesPadExists(req.params.pad, function(err, exists)
{
if(!exists) {
padManager.doesPadExists(req.params.pad, function(err, exists) {
if (!exists) {
return next();
}

View file

@ -5,52 +5,45 @@ var hasPadAccess = require("../../padaccess");
var exporthtml = require("../../utils/ExportHtml");
exports.expressCreateServer = function (hook_name, args, cb) {
//serve read only pad
args.app.get('/ro/:id', function(req, res)
{
// serve read only pad
args.app.get('/ro/:id', function(req, res) {
var html;
var padId;
async.series([
//translate the read only pad to a padId
function(callback)
{
readOnlyManager.getPadId(req.params.id, function(err, _padId)
{
// translate the read only pad to a padId
function(callback) {
readOnlyManager.getPadId(req.params.id, function(err, _padId) {
if(ERR(err, callback)) return;
padId = _padId;
//we need that to tell hasPadAcess about the pad
// we need that to tell hasPadAcess about the pad
req.params.pad = padId;
callback();
});
},
//render the html document
function(callback)
{
//return if the there is no padId
if(padId == null)
{
// render the html document
function(callback) {
// return if the there is no padId
if(padId == null) {
callback("notfound");
return;
}
hasPadAccess(req, res, function()
{
//render the html document
exporthtml.getPadHTMLDocument(padId, null, function(err, _html)
{
hasPadAccess(req, res, function() {
// render the html document
exporthtml.getPadHTMLDocument(padId, null, function(err, _html) {
if(ERR(err, callback)) return;
html = _html;
callback();
});
});
}
], function(err)
{
//throw any unexpected error
],
function(err) {
// throw any unexpected error
if(err && err != "notfound")
ERR(err);

View file

@ -2,29 +2,26 @@ var padManager = require('../../db/PadManager');
var url = require('url');
exports.expressCreateServer = function (hook_name, args, cb) {
//redirects browser to the pad's sanitized url if needed. otherwise, renders the html
// redirects browser to the pad's sanitized url if needed. otherwise, renders the html
args.app.param('pad', function (req, res, next, padId) {
//ensure the padname is valid and the url doesn't end with a /
if(!padManager.isValidPadId(padId) || /\/$/.test(req.url))
{
// ensure the padname is valid and the url doesn't end with a /
if (!padManager.isValidPadId(padId) || /\/$/.test(req.url)) {
res.status(404).send('Such a padname is forbidden');
return;
}
padManager.sanitizePadId(padId, function(sanitizedPadId) {
//the pad id was sanitized, so we redirect to the sanitized version
if(sanitizedPadId != padId)
{
if (sanitizedPadId != padId) {
// the pad id was sanitized, so we redirect to the sanitized version
var real_url = sanitizedPadId;
real_url = encodeURIComponent(real_url);
var query = url.parse(req.url).query;
if ( query ) real_url += '?' + query;
res.header('Location', real_url);
res.status(302).send('You should be redirected to <a href="' + real_url + '">' + real_url + '</a>');
}
//the pad id was fine, so just render it
else
{
} else {
// the pad id was fine, so just render it
next();
}
});

View file

@ -4,37 +4,35 @@ var path = require("path")
, async = require("async");
exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/tests/frontend/specs_list.js', function(req, res){
args.app.get('/tests/frontend/specs_list.js', function(req, res) {
async.parallel({
coreSpecs: function(callback){
coreSpecs: function(callback) {
exports.getCoreTests(callback);
},
pluginSpecs: function(callback){
pluginSpecs: function(callback) {
exports.getPluginTests(callback);
}
},
function(err, results){
function(err, results) {
var files = results.coreSpecs; // push the core specs to a file object
files = files.concat(results.pluginSpecs); // add the plugin Specs to the core specs
console.debug("Sent browser the following test specs:", files.sort());
res.send("var specs_list = " + JSON.stringify(files.sort()) + ";\n");
});
});
// path.join seems to normalize by default, but we'll just be explicit
var rootTestFolder = path.normalize(path.join(npm.root, "../tests/frontend/"));
var url2FilePath = function(url){
var url2FilePath = function(url) {
var subPath = url.substr("/tests/frontend".length);
if (subPath == ""){
if (subPath == "") {
subPath = "index.html"
}
subPath = subPath.split("?")[0];
var filePath = path.normalize(path.join(rootTestFolder, subPath));
// make sure we jail the paths to the test folder, otherwise serve index
if (filePath.indexOf(rootTestFolder) !== 0) {
filePath = path.join(rootTestFolder, "index.html");
@ -46,8 +44,8 @@ exports.expressCreateServer = function (hook_name, args, cb) {
var specFilePath = url2FilePath(req.url);
var specFileName = path.basename(specFilePath);
fs.readFile(specFilePath, function(err, content){
if(err){ return res.send(500); }
fs.readFile(specFilePath, function(err, content) {
if (err) { return res.send(500); }
content = "describe(" + JSON.stringify(specFileName) + ", function(){ " + content + " });";
@ -65,16 +63,18 @@ exports.expressCreateServer = function (hook_name, args, cb) {
});
}
exports.getPluginTests = function(callback){
exports.getPluginTests = function(callback) {
var pluginSpecs = [];
var plugins = fs.readdirSync('node_modules');
plugins.forEach(function(plugin){
if(fs.existsSync("node_modules/"+plugin+"/static/tests/frontend/specs")){ // if plugins exists
var specFiles = fs.readdirSync("node_modules/"+plugin+"/static/tests/frontend/specs/");
async.forEach(specFiles, function(spec){ // for each specFile push it to pluginSpecs
pluginSpecs.push("/static/plugins/"+plugin+"/static/tests/frontend/specs/" + spec);
plugins.forEach(function(plugin) {
if (fs.existsSync("node_modules/" + plugin + "/static/tests/frontend/specs")) {
// if plugins exists
var specFiles = fs.readdirSync("node_modules/" + plugin + "/static/tests/frontend/specs/");
async.forEach(specFiles, function(spec) {
// for each specFile push it to pluginSpecs
pluginSpecs.push("/static/plugins/" + plugin + "/static/tests/frontend/specs/" + spec);
},
function(err){
function(err) {
// blow up if something bad happens!
});
}
@ -82,10 +82,11 @@ exports.getPluginTests = function(callback){
callback(null, pluginSpecs);
}
exports.getCoreTests = function(callback){
fs.readdir('tests/frontend/specs', function(err, coreSpecs){ // get the core test specs
if(err){ return res.send(500); }
exports.getCoreTests = function(callback) {
// get the core test specs
fs.readdir('tests/frontend/specs', function(err, coreSpecs) {
if (err) { return res.send(500); }
callback(null, coreSpecs);
});
}

View file

@ -1,16 +1,16 @@
var ERR = require("async-stacktrace");
var securityManager = require('./db/SecurityManager');
//checks for padAccess
// checks for padAccess
module.exports = function (req, res, callback) {
securityManager.checkAccess(req.params.pad, req.cookies.sessionID, req.cookies.token, req.cookies.password, function(err, accessObj) {
if(ERR(err, callback)) return;
if (ERR(err, callback)) return;
//there is access, continue
if(accessObj.accessStatus == "grant") {
if (accessObj.accessStatus == "grant") {
// there is access, continue
callback();
//no access
} else {
// no access
res.status(403).send("403 - Can't touch this");
}
});

View file

@ -46,8 +46,8 @@ NodeVersion.enforceMinNodeVersion('8.9.0');
*/
var stats = require('./stats');
stats.gauge('memoryUsage', function() {
return process.memoryUsage().rss
})
return process.memoryUsage().rss;
});
var settings
, db
@ -59,8 +59,8 @@ async.waterfall([
// load npm
function(callback) {
npm.load({}, function(er) {
callback(er)
})
callback(er);
});
},
// load everything
@ -73,14 +73,13 @@ async.waterfall([
callback();
},
//initalize the database
function (callback)
{
// initalize the database
function (callback) {
db.init(callback);
},
function(callback) {
plugins.update(callback)
plugins.update(callback);
},
function (callback) {
@ -93,9 +92,8 @@ async.waterfall([
callback();
},
//initalize the http server
function (callback)
{
// initalize the http server
function (callback) {
hooks.callAll("createServer", {});
callback(null);
}

View file

@ -30,40 +30,32 @@ function getPadTXT(pad, revNum, callback)
var atext = pad.atext;
var html;
async.waterfall([
// fetch revision atext
function(callback) {
if (revNum != undefined) {
pad.getInternalRevisionAText(revNum, function(err, revisionAtext) {
if (ERR(err, callback)) return;
function (callback)
{
if (revNum != undefined)
{
pad.getInternalRevisionAText(revNum, function (err, revisionAtext)
{
if(ERR(err, callback)) return;
atext = revisionAtext;
callback();
});
}
else
{
} else {
callback(null);
}
},
// convert atext to html
function (callback)
{
html = getTXTFromAtext(pad, atext); // only this line is different to the HTML function
function(callback) {
// only this line is different to the HTML function
html = getTXTFromAtext(pad, atext);
callback(null);
}],
// run final callback
function(err) {
if (ERR(err, callback)) return;
function (err)
{
if(ERR(err, callback)) return;
callback(null, html);
});
}
@ -80,17 +72,14 @@ function getTXTFromAtext(pad, atext, authorColors)
var anumMap = {};
var css = "";
props.forEach(function (propName, i)
{
props.forEach(function(propName, i) {
var propTrueNum = apool.putAttrib([propName, true], true);
if (propTrueNum >= 0)
{
if (propTrueNum >= 0) {
anumMap[propTrueNum] = i;
}
});
function getLineTXT(text, attribs)
{
function getLineTXT(text, attribs) {
var propVals = [false, false, false];
var ENTER = 1;
var STAY = 2;
@ -106,94 +95,77 @@ function getTXTFromAtext(pad, atext, authorColors)
var idx = 0;
function processNextChars(numChars)
{
if (numChars <= 0)
{
function processNextChars(numChars) {
if (numChars <= 0) {
return;
}
var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
idx += numChars;
while (iter.hasNext())
{
while (iter.hasNext()) {
var o = iter.next();
var propChanged = false;
Changeset.eachAttribNumber(o.attribs, function (a)
{
if (a in anumMap)
{
Changeset.eachAttribNumber(o.attribs, function(a) {
if (a in anumMap) {
var i = anumMap[a]; // i = 0 => bold, etc.
if (!propVals[i])
{
if (!propVals[i]) {
propVals[i] = ENTER;
propChanged = true;
}
else
{
} else {
propVals[i] = STAY;
}
}
});
for (var i = 0; i < propVals.length; i++)
{
if (propVals[i] === true)
{
for (var i = 0; i < propVals.length; i++) {
if (propVals[i] === true) {
propVals[i] = LEAVE;
propChanged = true;
}
else if (propVals[i] === STAY)
{
propVals[i] = true; // set it back
} else if (propVals[i] === STAY) {
// set it back
propVals[i] = true;
}
}
// now each member of propVal is in {false,LEAVE,ENTER,true}
// according to what happens at start of span
if (propChanged)
{
if (propChanged) {
// leaving bold (e.g.) also leaves italics, etc.
var left = false;
for (var i = 0; i < propVals.length; i++)
{
for (var i = 0; i < propVals.length; i++) {
var v = propVals[i];
if (!left)
{
if (v === LEAVE)
{
if (!left) {
if (v === LEAVE) {
left = true;
}
}
else
{
if (v === true)
{
propVals[i] = STAY; // tag will be closed and re-opened
} else {
if (v === true) {
// tag will be closed and re-opened
propVals[i] = STAY;
}
}
}
var tags2close = [];
for (var i = propVals.length - 1; i >= 0; i--)
{
if (propVals[i] === LEAVE)
{
for (var i = propVals.length - 1; i >= 0; i--) {
if (propVals[i] === LEAVE) {
//emitCloseTag(i);
tags2close.push(i);
propVals[i] = false;
}
else if (propVals[i] === STAY)
{
} else if (propVals[i] === STAY) {
//emitCloseTag(i);
tags2close.push(i);
}
}
for (var i = 0; i < propVals.length; i++)
{
if (propVals[i] === ENTER || propVals[i] === STAY)
{
for (var i = 0; i < propVals.length; i++) {
if (propVals[i] === ENTER || propVals[i] === STAY) {
propVals[i] = true;
}
}
@ -201,9 +173,9 @@ function getTXTFromAtext(pad, atext, authorColors)
} // end if (propChanged)
var chars = o.chars;
if (o.lines)
{
chars--; // exclude newline at end of line, if present
if (o.lines) {
// exclude newline at end of line, if present
chars--;
}
var s = taker.take(chars);
@ -220,19 +192,19 @@ function getTXTFromAtext(pad, atext, authorColors)
} // end iteration over spans in line
var tags2close = [];
for (var i = propVals.length - 1; i >= 0; i--)
{
if (propVals[i])
{
for (var i = propVals.length - 1; i >= 0; i--) {
if (propVals[i]) {
tags2close.push(i);
propVals[i] = false;
}
}
} // end processNextChars
processNextChars(text.length - idx);
return(assem.toString());
} // end getLineHTML
var pieces = [css];
// Need to deal with constraints imposed on HTML lists; can
@ -242,41 +214,44 @@ function getTXTFromAtext(pad, atext, authorColors)
// so we want to do something reasonable there. We also
// want to deal gracefully with blank lines.
// => keeps track of the parents level of indentation
for (var i = 0; i < textLines.length; i++)
{
for (var i = 0; i < textLines.length; i++) {
var line = _analyzeLine(textLines[i], attribLines[i], apool);
var lineContent = getLineTXT(line.text, line.aline);
if(line.listTypeName == "bullet"){
if (line.listTypeName == "bullet") {
lineContent = "* " + lineContent; // add a bullet
}
if(line.listLevel > 0){
for (var j = line.listLevel - 1; j >= 0; j--){
if (line.listLevel > 0) {
for (var j = line.listLevel - 1; j >= 0; j--) {
pieces.push('\t');
}
if(line.listTypeName == "number"){
if (line.listTypeName == "number") {
pieces.push(line.listLevel + ". ");
// This is bad because it doesn't truly reflect what the user
// sees because browsers do magic on nested <ol><li>s
}
pieces.push(lineContent, '\n');
}else{
} else {
pieces.push(lineContent, '\n');
}
}
return pieces.join('');
}
exports.getTXTFromAtext = getTXTFromAtext;
exports.getPadTXTDocument = function (padId, revNum, callback)
exports.getPadTXTDocument = function(padId, revNum, callback)
{
padManager.getPad(padId, function (err, pad)
{
if(ERR(err, callback)) return;
padManager.getPad(padId, function(err, pad) {
if (ERR(err, callback)) return;
getPadTXT(pad, revNum, function(err, html) {
if (ERR(err, callback)) return;
getPadTXT(pad, revNum, function (err, html)
{
if(ERR(err, callback)) return;
callback(null, html);
});
});

View file

@ -18,57 +18,56 @@ var log4js = require('log4js');
var async = require("async");
var db = require("../db/DB").db;
exports.setPadRaw = function(padId, records, callback){
exports.setPadRaw = function(padId, records, callback)
{
records = JSON.parse(records);
async.eachSeries(Object.keys(records), function(key, cb){
var value = records[key]
async.eachSeries(Object.keys(records), function(key, cb) {
var value = records[key];
if(!value){
if (!value) {
return setImmediate(cb);
}
// Author data
if(value.padIDs){
// rewrite author pad ids
if (value.padIDs) {
// Author data - rewrite author pad ids
value.padIDs[padId] = 1;
var newKey = key;
// Does this author already exist?
db.get(key, function(err, author){
if(author){
// Yes, add the padID to the author..
if( Object.prototype.toString.call(author) === '[object Array]'){
db.get(key, function(err, author) {
if (author) {
// Yes, add the padID to the author
if (Object.prototype.toString.call(author) === '[object Array]') {
author.padIDs.push(padId);
}
value = author;
}else{
} else {
// No, create a new array with the author info in
value.padIDs = [padId];
}
});
} else {
// Not author data, probably pad data
}else{
// we can split it to look to see if its pad data
// we can split it to look to see if it's pad data
var oldPadId = key.split(":");
// we know its pad data..
if(oldPadId[0] === "pad"){
// we know it's pad data
if (oldPadId[0] === "pad") {
// so set the new pad id for the author
oldPadId[1] = padId;
// and create the value
var newKey = oldPadId.join(":"); // create the new key
}
}
// Write the value to the server
db.set(newKey, value);
setImmediate(cb);
}, function(){
},
function() {
callback(null, true);
});
}

View file

@ -36,19 +36,22 @@ function setPadHTML(pad, html, callback)
// Convert a dom tree into a list of lines and attribute liens
// using the content collector object
var cc = contentcollector.makeContentCollector(true, null, pad.pool);
try{ // we use a try here because if the HTML is bad it will blow up
try {
// we use a try here because if the HTML is bad it will blow up
cc.collectContent(doc);
}catch(e){
} catch(e) {
apiLogger.warn("HTML was not properly formed", e);
return callback(e); // We don't process the HTML because it was bad..
// don't process the HTML because it was bad
return callback(e);
}
var result = cc.finish();
apiLogger.debug('Lines:');
var i;
for (i = 0; i < result.lines.length; i += 1)
{
for (i = 0; i < result.lines.length; i += 1) {
apiLogger.debug('Line ' + (i + 1) + ' text: ' + result.lines[i]);
apiLogger.debug('Line ' + (i + 1) + ' attributes: ' + result.lineAttribs[i]);
}
@ -59,18 +62,15 @@ function setPadHTML(pad, html, callback)
apiLogger.debug(newText);
var newAttribs = result.lineAttribs.join('|1+1') + '|1+1';
function eachAttribRun(attribs, func /*(startInNewText, endInNewText, attribs)*/ )
{
function eachAttribRun(attribs, func /*(startInNewText, endInNewText, attribs)*/ ) {
var attribsIter = Changeset.opIterator(attribs);
var textIndex = 0;
var newTextStart = 0;
var newTextEnd = newText.length;
while (attribsIter.hasNext())
{
while (attribsIter.hasNext()) {
var op = attribsIter.next();
var nextIndex = textIndex + op.chars;
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd))
{
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
}
textIndex = nextIndex;
@ -81,13 +81,13 @@ function setPadHTML(pad, html, callback)
var builder = Changeset.builder(1);
// assemble each line into the builder
eachAttribRun(newAttribs, function(start, end, attribs)
{
eachAttribRun(newAttribs, function(start, end, attribs) {
builder.insert(newText.substring(start, end), attribs);
});
// the changeset is ready!
var theChangeset = builder.toString();
apiLogger.debug('The changeset: ' + theChangeset);
pad.setText("\n");
pad.appendRevision(theChangeset);

View file

@ -2,17 +2,18 @@ var Changeset = require("../../static/js/Changeset");
var async = require("async");
var exportHtml = require('./ExportHtml');
function PadDiff (pad, fromRev, toRev){
//check parameters
if(!pad || !pad.id || !pad.atext || !pad.pool)
{
function PadDiff (pad, fromRev, toRev) {
// check parameters
if (!pad || !pad.id || !pad.atext || !pad.pool) {
throw new Error('Invalid pad');
}
var range = pad.getValidRevisionRange(fromRev, toRev);
if(!range) { throw new Error('Invalid revision range.' +
if (!range) {
throw new Error('Invalid revision range.' +
' startRev: ' + fromRev +
' endRev: ' + toRev); }
' endRev: ' + toRev);
}
this._pad = pad;
this._fromRev = range.startRev;
@ -21,63 +22,70 @@ function PadDiff (pad, fromRev, toRev){
this._authors = [];
}
PadDiff.prototype._isClearAuthorship = function(changeset){
//unpack
PadDiff.prototype._isClearAuthorship = function(changeset) {
// unpack
var unpacked = Changeset.unpack(changeset);
//check if there is nothing in the charBank
if(unpacked.charBank !== "")
// check if there is nothing in the charBank
if (unpacked.charBank !== "") {
return false;
}
//check if oldLength == newLength
if(unpacked.oldLen !== unpacked.newLen)
// check if oldLength == newLength
if (unpacked.oldLen !== unpacked.newLen) {
return false;
}
//lets iterator over the operators
// lets iterator over the operators
var iterator = Changeset.opIterator(unpacked.ops);
//get the first operator, this should be a clear operator
// get the first operator, this should be a clear operator
var clearOperator = iterator.next();
//check if there is only one operator
if(iterator.hasNext() === true)
// check if there is only one operator
if (iterator.hasNext() === true) {
return false;
}
//check if this operator doesn't change text
if(clearOperator.opcode !== "=")
// check if this operator doesn't change text
if (clearOperator.opcode !== "=") {
return false;
}
//check that this operator applys to the complete text
//if the text ends with a new line, its exactly one character less, else it has the same length
if(clearOperator.chars !== unpacked.oldLen-1 && clearOperator.chars !== unpacked.oldLen)
// check that this operator applys to the complete text
// if the text ends with a new line, its exactly one character less, else it has the same length
if (clearOperator.chars !== unpacked.oldLen-1 && clearOperator.chars !== unpacked.oldLen) {
return false;
}
var attributes = [];
Changeset.eachAttribNumber(changeset, function(attrNum){
Changeset.eachAttribNumber(changeset, function(attrNum) {
attributes.push(attrNum);
});
//check that this changeset uses only one attribute
if(attributes.length !== 1)
// check that this changeset uses only one attribute
if (attributes.length !== 1) {
return false;
}
var appliedAttribute = this._pad.pool.getAttrib(attributes[0]);
//check if the applied attribute is an anonymous author attribute
if(appliedAttribute[0] !== "author" || appliedAttribute[1] !== "")
// check if the applied attribute is an anonymous author attribute
if (appliedAttribute[0] !== "author" || appliedAttribute[1] !== "") {
return false;
}
return true;
};
PadDiff.prototype._createClearAuthorship = function(rev, callback){
PadDiff.prototype._createClearAuthorship = function(rev, callback) {
var self = this;
this._pad.getInternalRevisionAText(rev, function(err, atext){
if(err){
this._pad.getInternalRevisionAText(rev, function(err, atext) {
if (err) {
return callback(err);
}
//build clearAuthorship changeset
// build clearAuthorship changeset
var builder = Changeset.builder(atext.text.length);
builder.keepText(atext.text, [['author','']], self._pad.pool);
var changeset = builder.toString();
@ -86,23 +94,23 @@ PadDiff.prototype._createClearAuthorship = function(rev, callback){
});
};
PadDiff.prototype._createClearStartAtext = function(rev, callback){
PadDiff.prototype._createClearStartAtext = function(rev, callback) {
var self = this;
//get the atext of this revision
this._pad.getInternalRevisionAText(rev, function(err, atext){
if(err){
// get the atext of this revision
this._pad.getInternalRevisionAText(rev, function(err, atext) {
if (err) {
return callback(err);
}
//create the clearAuthorship changeset
self._createClearAuthorship(rev, function(err, changeset){
if(err){
// create the clearAuthorship changeset
self._createClearAuthorship(rev, function(err, changeset) {
if (err) {
return callback(err);
}
try {
//apply the clearAuthorship changeset
// apply the clearAuthorship changeset
var newAText = Changeset.applyToAText(changeset, atext, self._pad.pool);
} catch(err) {
return callback(err)
@ -116,18 +124,18 @@ PadDiff.prototype._createClearStartAtext = function(rev, callback){
PadDiff.prototype._getChangesetsInBulk = function(startRev, count, callback) {
var self = this;
//find out which revisions we need
// find out which revisions we need
var revisions = [];
for(var i=startRev;i<(startRev+count) && i<=this._pad.head;i++){
for (var i = startRev; i < (startRev + count) && i <= this._pad.head; i++) {
revisions.push(i);
}
var changesets = [], authors = [];
//get all needed revisions
async.forEach(revisions, function(rev, callback){
self._pad.getRevision(rev, function(err, revision){
if(err){
// get all needed revisions
async.forEach(revisions, function(rev, callback) {
self._pad.getRevision(rev, function(err, revision) {
if (err) {
return callback(err);
}
@ -138,16 +146,18 @@ PadDiff.prototype._getChangesetsInBulk = function(startRev, count, callback) {
callback();
});
}, function(err){
},
function(err) {
callback(err, changesets, authors);
});
};
PadDiff.prototype._addAuthors = function(authors) {
var self = this;
//add to array if not in the array
authors.forEach(function(author){
if(self._authors.indexOf(author) == -1){
// add to array if not in the array
authors.forEach(function(author) {
if (self._authors.indexOf(author) == -1) {
self._authors.push(author);
}
});
@ -157,67 +167,67 @@ PadDiff.prototype._createDiffAtext = function(callback) {
var self = this;
var bulkSize = 100;
//get the cleaned startAText
self._createClearStartAtext(self._fromRev, function(err, atext){
if(err) { return callback(err); }
// get the cleaned startAText
self._createClearStartAtext(self._fromRev, function(err, atext) {
if (err) { return callback(err); }
var superChangeset = null;
var rev = self._fromRev + 1;
//async while loop
// async while loop
async.whilst(
//loop condition
// loop condition
function () { return rev <= self._toRev; },
//loop body
// loop body
function (callback) {
//get the bulk
self._getChangesetsInBulk(rev,bulkSize,function(err, changesets, authors){
// get the bulk
self._getChangesetsInBulk(rev,bulkSize,function(err, changesets, authors) {
var addedAuthors = [];
//run trough all changesets
for(var i=0;i<changesets.length && (rev+i)<=self._toRev;i++){
// run trough all changesets
for (var i = 0; i < changesets.length && (rev + i) <= self._toRev; i++) {
var changeset = changesets[i];
//skip clearAuthorship Changesets
if(self._isClearAuthorship(changeset)){
// skip clearAuthorship Changesets
if (self._isClearAuthorship(changeset)) {
continue;
}
changeset = self._extendChangesetWithAuthor(changeset, authors[i], self._pad.pool);
//add this author to the authorarray
// add this author to the authorarray
addedAuthors.push(authors[i]);
//compose it with the superChangset
if(superChangeset === null){
// compose it with the superChangset
if (superChangeset === null) {
superChangeset = changeset;
} else {
superChangeset = Changeset.composeWithDeletions(superChangeset, changeset, self._pad.pool);
}
}
//add the authors to the PadDiff authorArray
// add the authors to the PadDiff authorArray
self._addAuthors(addedAuthors);
//lets continue with the next bulk
// lets continue with the next bulk
rev += bulkSize;
callback();
});
},
//after the loop has ended
// after the loop has ended
function (err) {
//if there are only clearAuthorship changesets, we don't get a superChangeset, so we can skip this step
if(superChangeset){
// if there are only clearAuthorship changesets, we don't get a superChangeset, so we can skip this step
if (superChangeset) {
var deletionChangeset = self._createDeletionChangeset(superChangeset,atext,self._pad.pool);
try {
//apply the superChangeset, which includes all addings
atext = Changeset.applyToAText(superChangeset,atext,self._pad.pool);
//apply the deletionChangeset, which adds a deletions
atext = Changeset.applyToAText(deletionChangeset,atext,self._pad.pool);
// apply the superChangeset, which includes all addings
atext = Changeset.applyToAText(superChangeset, atext, self._pad.pool);
// apply the deletionChangeset, which adds a deletions
atext = Changeset.applyToAText(deletionChangeset, atext, self._pad.pool);
} catch(err) {
return callback(err)
}
@ -229,9 +239,9 @@ PadDiff.prototype._createDiffAtext = function(callback) {
});
};
PadDiff.prototype.getHtml = function(callback){
//cache the html
if(this._html != null){
PadDiff.prototype.getHtml = function(callback) {
// cache the html
if (this._html != null) {
return callback(null, this._html);
}
@ -239,10 +249,10 @@ PadDiff.prototype.getHtml = function(callback){
var atext, html, authorColors;
async.series([
//get the diff atext
function(callback){
self._createDiffAtext(function(err, _atext){
if(err){
// get the diff atext
function(callback) {
self._createDiffAtext(function(err, _atext) {
if (err) {
return callback(err);
}
@ -250,10 +260,11 @@ PadDiff.prototype.getHtml = function(callback){
callback();
});
},
//get the authorColor table
function(callback){
self._pad.getAllAuthorColors(function(err, _authorColors){
if(err){
// get the authorColor table
function(callback) {
self._pad.getAllAuthorColors(function(err, _authorColors) {
if (err) {
return callback(err);
}
@ -261,24 +272,26 @@ PadDiff.prototype.getHtml = function(callback){
callback();
});
},
//convert the atext to html
function(callback){
// convert the atext to html
function(callback) {
html = exportHtml.getHTMLFromAtext(self._pad, atext, authorColors);
self._html = html;
callback();
}
], function(err){
],
function(err) {
callback(err, html);
});
};
PadDiff.prototype.getAuthors = function(callback){
PadDiff.prototype.getAuthors = function(callback) {
var self = this;
//check if html was already produced, if not produce it, this generates the author array at the same time
if(self._html == null){
self.getHtml(function(err){
if(err){
// check if html was already produced, if not produce it, this generates the author array at the same time
if (self._html == null) {
self.getHtml(function(err) {
if (err) {
return callback(err);
}
@ -290,39 +303,38 @@ PadDiff.prototype.getAuthors = function(callback){
};
PadDiff.prototype._extendChangesetWithAuthor = function(changeset, author, apool) {
//unpack
// unpack
var unpacked = Changeset.unpack(changeset);
var iterator = Changeset.opIterator(unpacked.ops);
var assem = Changeset.opAssembler();
//create deleted attribs
// create deleted attribs
var authorAttrib = apool.putAttrib(["author", author || ""]);
var deletedAttrib = apool.putAttrib(["removed", true]);
var attribs = "*" + Changeset.numToString(authorAttrib) + "*" + Changeset.numToString(deletedAttrib);
//iteratore over the operators of the changeset
while(iterator.hasNext()){
// iteratore over the operators of the changeset
while(iterator.hasNext()) {
var operator = iterator.next();
//this is a delete operator, extend it with the author
if(operator.opcode === "-"){
if (operator.opcode === "-") {
// this is a delete operator, extend it with the author
operator.attribs = attribs;
}
//this is operator changes only attributes, let's mark which author did that
else if(operator.opcode === "=" && operator.attribs){
} else if (operator.opcode === "=" && operator.attribs) {
// this is operator changes only attributes, let's mark which author did that
operator.attribs+="*"+Changeset.numToString(authorAttrib);
}
//append the new operator to our assembler
// append the new operator to our assembler
assem.append(operator);
}
//return the modified changeset
// return the modified changeset
return Changeset.pack(unpacked.oldLen, unpacked.newLen, assem.toString(), unpacked.charBank);
};
//this method is 80% like Changeset.inverse. I just changed so instead of reverting, it adds deletions and attribute changes to to the atext.
// this method is 80% like Changeset.inverse. I just changed so instead of reverting, it adds deletions and attribute changes to to the atext.
PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
var lines = Changeset.splitTextLines(startAText.text);
var alines = Changeset.splitAttributionLines(startAText.attribs, startAText.text);
@ -384,10 +396,13 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
curLineNextOp.chars = 0;
curLineOpIter = Changeset.opIterator(alines_get(curLine));
}
if (!curLineNextOp.chars) {
curLineOpIter.next(curLineNextOp);
}
var charsToUse = Math.min(numChars, curLineNextOp.chars);
func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && curLineNextOp.lines > 0);
numChars -= charsToUse;
curLineNextOp.chars -= charsToUse;
@ -421,6 +436,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
assem.append(firstString);
var lineNum = curLine + 1;
while (len < numChars) {
var nextString = lines_get(lineNum);
len += nextString.length;
@ -433,6 +449,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
function cachedStrFunc(func) {
var cache = {};
return function (s) {
if (!cache[s]) {
cache[s] = func(s);
@ -444,7 +461,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
var attribKeys = [];
var attribValues = [];
//iterate over all operators of this changeset
// iterate over all operators of this changeset
while (csIter.hasNext()) {
var csOp = csIter.next();
@ -463,7 +480,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
attribKeys.push(apool.getAttribKey(n));
attribValues.push(apool.getAttribValue(n));
if(apool.getAttribKey(n) === "author"){
if (apool.getAttribKey(n) === "author") {
authorAttrib = n;
}
});
@ -474,10 +491,12 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
var appliedKey = attribKeys[i];
var appliedValue = attribValues[i];
var oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, apool);
if (appliedValue != oldValue) {
backAttribs.push([appliedKey, oldValue]);
}
}
return Changeset.makeAttribsString('=', backAttribs, apool);
});
@ -485,11 +504,11 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
var textLeftToProcess = textBank;
while(textLeftToProcess.length > 0){
//process till the next line break or process only one line break
while(textLeftToProcess.length > 0) {
// process till the next line break or process only one line break
var lengthToProcess = textLeftToProcess.indexOf("\n");
var lineBreak = false;
switch(lengthToProcess){
switch(lengthToProcess) {
case -1:
lengthToProcess=textLeftToProcess.length;
break;
@ -499,20 +518,21 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
break;
}
//get the text we want to procceed in this step
// get the text we want to procceed in this step
var processText = textLeftToProcess.substr(0, lengthToProcess);
textLeftToProcess = textLeftToProcess.substr(lengthToProcess);
if(lineBreak){
builder.keep(1, 1); //just skip linebreaks, don't do a insert + keep for a linebreak
if (lineBreak) {
builder.keep(1, 1); // just skip linebreaks, don't do a insert + keep for a linebreak
//consume the attributes of this linebreak
consumeAttribRuns(1, function(){});
// consume the attributes of this linebreak
consumeAttribRuns(1, function() {});
} else {
//add the old text via an insert, but add a deletion attribute + the author attribute of the author who deleted it
// add the old text via an insert, but add a deletion attribute + the author attribute of the author who deleted it
var textBankIndex = 0;
consumeAttribRuns(lengthToProcess, function (len, attribs, endsLine) {
//get the old attributes back
// get the old attributes back
var attribs = (undoBackToAttribs(attribs) || "") + oldAttribsAddition;
builder.insert(processText.substr(textBankIndex, len), attribs);
@ -542,5 +562,5 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
return Changeset.checkRep(builder.toString());
};
//export the constructor
// export the constructor
module.exports = PadDiff;

View file

@ -4,12 +4,14 @@ var npm = require("npm");
var request = require("request");
var npmIsLoaded = false;
var withNpm = function (npmfn) {
if(npmIsLoaded) return npmfn();
npm.load({}, function (er) {
var withNpm = function(npmfn) {
if (npmIsLoaded) return npmfn();
npm.load({}, function(er) {
if (er) return npmfn(er);
npmIsLoaded = true;
npm.on("log", function (message) {
npm.on("log", function(message) {
console.log('npm: ',message)
});
npmfn();
@ -17,26 +19,33 @@ var withNpm = function (npmfn) {
}
var tasks = 0
function wrapTaskCb(cb) {
tasks++
tasks++;
return function() {
cb && cb.apply(this, arguments);
tasks--;
if(tasks == 0) onAllTasksFinished();
if (tasks == 0) onAllTasksFinished();
}
}
function onAllTasksFinished() {
hooks.aCallAll("restartServer", {}, function () {});
hooks.aCallAll("restartServer", {}, function() {});
}
exports.uninstall = function(plugin_name, cb) {
cb = wrapTaskCb(cb);
withNpm(function (er) {
withNpm(function(er) {
if (er) return cb && cb(er);
npm.commands.uninstall([plugin_name], function (er) {
npm.commands.uninstall([plugin_name], function(er) {
if (er) return cb && cb(er);
hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er, data) {
hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function(er, data) {
if (er) return cb(er);
plugins.update(cb);
});
});
@ -44,13 +53,17 @@ exports.uninstall = function(plugin_name, cb) {
};
exports.install = function(plugin_name, cb) {
cb = wrapTaskCb(cb)
withNpm(function (er) {
cb = wrapTaskCb(cb);
withNpm(function(er) {
if (er) return cb && cb(er);
npm.commands.install([plugin_name], function (er) {
npm.commands.install([plugin_name], function(er) {
if (er) return cb && cb(er);
hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er, data) {
hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function(er, data) {
if (er) return cb(er);
plugins.update(cb);
});
});
@ -63,41 +76,53 @@ var cacheTimestamp = 0;
exports.getAvailablePlugins = function(maxCacheAge, cb) {
request("https://static.etherpad.org/plugins.json", function(er, response, plugins){
if (er) return cb && cb(er);
if(exports.availablePlugins && maxCacheAge && Math.round(+new Date/1000)-cacheTimestamp <= maxCacheAge) {
return cb && cb(null, exports.availablePlugins)
if (exports.availablePlugins && maxCacheAge && Math.round(+ new Date / 1000) - cacheTimestamp <= maxCacheAge) {
return cb && cb(null, exports.availablePlugins);
}
try {
plugins = JSON.parse(plugins);
} catch (err) {
console.error('error parsing plugins.json:', err);
plugins = [];
}
exports.availablePlugins = plugins;
cacheTimestamp = Math.round(+new Date/1000);
cb && cb(null, plugins)
cacheTimestamp = Math.round(+ new Date / 1000);
cb && cb(null, plugins);
});
};
exports.search = function(searchTerm, maxCacheAge, cb) {
exports.getAvailablePlugins(maxCacheAge, function(er, results) {
if(er) return cb && cb(er);
if (er) return cb && cb(er);
var res = {};
if (searchTerm)
if (searchTerm) {
searchTerm = searchTerm.toLowerCase();
for (var pluginName in results) { // for every available plugin
}
for (var pluginName in results) {
// for every available plugin
if (pluginName.indexOf(plugins.prefix) != 0) continue; // TODO: Also search in keywords here!
if(searchTerm && !~results[pluginName].name.toLowerCase().indexOf(searchTerm)
if (searchTerm && !~results[pluginName].name.toLowerCase().indexOf(searchTerm)
&& (typeof results[pluginName].description != "undefined" && !~results[pluginName].description.toLowerCase().indexOf(searchTerm) )
){
if(typeof results[pluginName].description === "undefined"){
) {
if (typeof results[pluginName].description === "undefined") {
console.debug('plugin without Description: %s', results[pluginName].name);
}
continue;
}
res[pluginName] = results[pluginName];
}
cb && cb(null, res)
})
cb && cb(null, res);
});
};

View file

@ -55,6 +55,7 @@ exports.formatHooks = function (hook_set_name) {
exports.callInit = function (cb) {
var hooks = require("./hooks");
async.map(
Object.keys(exports.plugins),
function (plugin_name, cb) {
@ -83,6 +84,7 @@ exports.update = function (cb) {
exports.getPackages(function (er, packages) {
var parts = [];
var plugins = {};
// Load plugin metadata ep.json
async.forEach(
Object.keys(packages),
@ -106,6 +108,7 @@ exports.getPackages = function (cb) {
var dir = path.resolve(npm.dir, '..');
readInstalled(dir, function (er, data) {
if (er) cb(er, null);
var packages = {};
function flatten(deps) {
_.chain(deps).keys().each(function (name) {