mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-06-16 19:24:53 -04:00
resolve merge conflict
This commit is contained in:
commit
58bbfd8a65
210 changed files with 12113 additions and 7505 deletions
|
@ -74,6 +74,124 @@ exports.listSessionsOfAuthor = sessionManager.listSessionsOfAuthor;
|
|||
/**PAD CONTENT FUNCTIONS*/
|
||||
/************************/
|
||||
|
||||
/**
|
||||
getAttributePool(padID) returns the attribute pool of a pad
|
||||
|
||||
Example returns:
|
||||
{
|
||||
"code":0,
|
||||
"message":"ok",
|
||||
"data": {
|
||||
"pool":{
|
||||
"numToAttrib":{
|
||||
"0":["author","a.X4m8bBWJBZJnWGSh"],
|
||||
"1":["author","a.TotfBPzov54ihMdH"],
|
||||
"2":["author","a.StiblqrzgeNTbK05"],
|
||||
"3":["bold","true"]
|
||||
},
|
||||
"attribToNum":{
|
||||
"author,a.X4m8bBWJBZJnWGSh":0,
|
||||
"author,a.TotfBPzov54ihMdH":1,
|
||||
"author,a.StiblqrzgeNTbK05":2,
|
||||
"bold,true":3
|
||||
},
|
||||
"nextNum":4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
exports.getAttributePool = function (padID, callback)
|
||||
{
|
||||
getPadSafe(padID, true, function(err, pad)
|
||||
{
|
||||
if (ERR(err, callback)) return;
|
||||
callback(null, {pool: pad.pool});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
getRevisionChangeset (padID, [rev])
|
||||
|
||||
get the changeset at a given revision, or last revision if 'rev' is not defined.
|
||||
|
||||
Example returns:
|
||||
{
|
||||
"code" : 0,
|
||||
"message" : "ok",
|
||||
"data" : "Z:1>6b|5+6b$Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at http://etherpad.org\n"
|
||||
}
|
||||
|
||||
*/
|
||||
exports.getRevisionChangeset = function(padID, rev, callback)
|
||||
{
|
||||
// check if rev is set
|
||||
if (typeof rev === "function")
|
||||
{
|
||||
callback = rev;
|
||||
rev = undefined;
|
||||
}
|
||||
|
||||
// check if rev is a number
|
||||
if (rev !== undefined && typeof rev !== "number")
|
||||
{
|
||||
// try to parse the number
|
||||
if (!isNaN(parseInt(rev)))
|
||||
{
|
||||
rev = parseInt(rev);
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(new customError("rev is not a number", "apierror"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ensure this is not a negative number
|
||||
if (rev !== undefined && rev < 0)
|
||||
{
|
||||
callback(new customError("rev is not a negative number", "apierror"));
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure this is not a float value
|
||||
if (rev !== undefined && !is_int(rev))
|
||||
{
|
||||
callback(new customError("rev is a float value", "apierror"));
|
||||
return;
|
||||
}
|
||||
|
||||
// get the pad
|
||||
getPadSafe(padID, true, function(err, pad)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
//the client asked for a special revision
|
||||
if(rev !== undefined)
|
||||
{
|
||||
//check if this is a valid revision
|
||||
if(rev > pad.getHeadRevisionNumber())
|
||||
{
|
||||
callback(new customError("rev is higher than the head revision of the pad","apierror"));
|
||||
return;
|
||||
}
|
||||
|
||||
//get the changeset for this revision
|
||||
pad.getRevisionChangeset(rev, function(err, changeset)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
callback(null, changeset);
|
||||
})
|
||||
}
|
||||
//the client wants the latest changeset, lets return it to him
|
||||
else
|
||||
{
|
||||
callback(null, {"changeset": pad.getRevisionChangeset(pad.getHeadRevisionNumber())});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
getText(padID, [rev]) returns the text of a pad
|
||||
|
||||
|
@ -326,8 +444,8 @@ exports.getChatHistory = function(padID, start, end, callback)
|
|||
// fall back to getting the whole chat-history if a parameter is missing
|
||||
if(!start || !end)
|
||||
{
|
||||
start = 0;
|
||||
end = pad.chatHead;
|
||||
start = 0;
|
||||
end = pad.chatHead;
|
||||
}
|
||||
|
||||
if(start >= chatHead && chatHead > 0)
|
||||
|
@ -438,6 +556,46 @@ exports.deletePad = function(padID, callback)
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
copyPad(sourceID, destinationID[, force=false]) copies a pad. If force is true,
|
||||
the destination will be overwritten if it exists.
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: {padID: destinationID}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.copyPad = function(sourceID, destinationID, force, callback)
|
||||
{
|
||||
getPadSafe(sourceID, true, function(err, pad)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
pad.copy(destinationID, force, callback);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
movePad(sourceID, destinationID[, force=false]) moves a pad. If force is true,
|
||||
the destination will be overwritten if it exists.
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: {padID: destinationID}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.movePad = function(sourceID, destinationID, force, callback)
|
||||
{
|
||||
getPadSafe(sourceID, true, function(err, pad)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
pad.copy(destinationID, force, function(err) {
|
||||
if(ERR(err, callback)) return;
|
||||
pad.remove(callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
getReadOnlyLink(padID) returns the read only link of a pad
|
||||
|
||||
|
@ -664,7 +822,7 @@ createDiffHTML(padID, startRev, endRev) returns an object of diffs from 2 points
|
|||
|
||||
Example returns:
|
||||
|
||||
{"code":0,"message":"ok","data":{"html":"<style>\n.authora_HKIv23mEbachFYfH {background-color: #a979d9}\n.authora_n4gEeMLsv1GivNeh {background-color: #a9b5d9}\n.removed {text-decoration: line-through; -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; filter: alpha(opacity=80); opacity: 0.8; }\n</style>Welcome to Etherpad Lite!<br><br>This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!<br><br>Get involved with Etherpad at <a href=\"http://etherpad.org\">http://etherpad.org</a><br><span class=\"authora_HKIv23mEbachFYfH\">aw</span><br><br>","authors":["a.HKIv23mEbachFYfH",""]}}
|
||||
{"code":0,"message":"ok","data":{"html":"<style>\n.authora_HKIv23mEbachFYfH {background-color: #a979d9}\n.authora_n4gEeMLsv1GivNeh {background-color: #a9b5d9}\n.removed {text-decoration: line-through; -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; filter: alpha(opacity=80); opacity: 0.8; }\n</style>Welcome to Etherpad!<br><br>This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!<br><br>Get involved with Etherpad at <a href=\"http://etherpad.org\">http://etherpad.org</a><br><span class=\"authora_HKIv23mEbachFYfH\">aw</span><br><br>","authors":["a.HKIv23mEbachFYfH",""]}}
|
||||
{"code":4,"message":"no or wrong API Key","data":null}
|
||||
*/
|
||||
exports.createDiffHTML = function(padID, startRev, endRev, callback){
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
var ERR = require("async-stacktrace");
|
||||
var db = require("./DB").db;
|
||||
var async = require("async");
|
||||
var customError = require("../utils/customError");
|
||||
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
||||
|
||||
exports.getColorPalette = function(){
|
||||
|
@ -272,4 +273,4 @@ exports.removePad = function (authorID, padID)
|
|||
db.set("globalAuthor:" + authorID, author);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -215,25 +215,32 @@ exports.createGroupIfNotExistsFor = function(groupMapper, callback)
|
|||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
// 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});
|
||||
|
||||
// hah, the returned group doesn't exist, let's create one
|
||||
createGroupForMapper(callback)
|
||||
})
|
||||
}
|
||||
//there is no group for this mapper, let's create a group
|
||||
if(groupID == null)
|
||||
{
|
||||
else {
|
||||
createGroupForMapper(callback)
|
||||
}
|
||||
|
||||
function createGroupForMapper(cb) {
|
||||
exports.createGroup(function(err, responseObj)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
if(ERR(err, cb)) return;
|
||||
|
||||
//create the mapper entry for this group
|
||||
db.set("mapper2group:"+groupMapper, responseObj.groupID);
|
||||
|
||||
callback(null, responseObj);
|
||||
cb(null, responseObj);
|
||||
});
|
||||
}
|
||||
//there is a group for this mapper, let's return it
|
||||
else
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
callback(null, {groupID: groupID});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ var settings = require('../utils/Settings');
|
|||
var authorManager = require("./AuthorManager");
|
||||
var padManager = require("./PadManager");
|
||||
var padMessageHandler = require("../handler/PadMessageHandler");
|
||||
var groupManager = require("./GroupManager");
|
||||
var customError = require("../utils/customError");
|
||||
var readOnlyManager = require("./ReadOnlyManager");
|
||||
var crypto = require("crypto");
|
||||
var randomString = require("../utils/randomstring");
|
||||
|
@ -404,6 +406,152 @@ Pad.prototype.init = function init(text, callback) {
|
|||
});
|
||||
};
|
||||
|
||||
Pad.prototype.copy = function copy(destinationID, force, callback) {
|
||||
var sourceID = this.id;
|
||||
var _this = this;
|
||||
|
||||
// make force optional
|
||||
if (typeof force == "function") {
|
||||
callback = force;
|
||||
force = false;
|
||||
}
|
||||
else if (force == undefined || force.toLowerCase() != "true") {
|
||||
force = false;
|
||||
}
|
||||
else force = true;
|
||||
|
||||
//kick everyone from this pad
|
||||
// TODO: this presents a message on the client saying that the pad was 'deleted'. Fix this?
|
||||
padMessageHandler.kickSessionsFromPad(sourceID);
|
||||
|
||||
// flush the source pad:
|
||||
_this.saveToDatabase();
|
||||
|
||||
async.series([
|
||||
// if it's a group pad, let's make sure the group exists.
|
||||
function(callback)
|
||||
{
|
||||
if (destinationID.indexOf("$") != -1)
|
||||
{
|
||||
groupManager.doesGroupExist(destinationID.split("$")[0], 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"));
|
||||
return;
|
||||
}
|
||||
//everything is fine, continue
|
||||
else
|
||||
{
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
callback();
|
||||
},
|
||||
// if the pad exists, we should abort, unless forced.
|
||||
function(callback)
|
||||
{
|
||||
console.log("destinationID", destinationID, force);
|
||||
padManager.doesPadExists(destinationID, function (err, exists)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
if(exists == true)
|
||||
{
|
||||
if (!force)
|
||||
{
|
||||
console.log("erroring out without force");
|
||||
callback(new customError("destinationID already exists","apierror"));
|
||||
console.log("erroring out without force - after");
|
||||
return;
|
||||
}
|
||||
else // exists and forcing
|
||||
{
|
||||
padManager.getPad(destinationID, function(err, pad) {
|
||||
if (ERR(err, callback)) return;
|
||||
pad.remove(callback);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
// copy the 'pad' entry
|
||||
function(callback)
|
||||
{
|
||||
db.get("pad:"+sourceID, function(err, pad) {
|
||||
db.set("pad:"+destinationID, pad);
|
||||
});
|
||||
callback();
|
||||
},
|
||||
//copy all relations
|
||||
function(callback)
|
||||
{
|
||||
async.parallel([
|
||||
//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) {
|
||||
if (ERR(err, callback)) return;
|
||||
db.set("pad:"+destinationID+":chat:"+i, chat);
|
||||
});
|
||||
}
|
||||
|
||||
callback();
|
||||
},
|
||||
//copy all revisions
|
||||
function(callback)
|
||||
{
|
||||
var revHead = _this.head;
|
||||
//console.log(revHead);
|
||||
for(var i=0;i<=revHead;i++)
|
||||
{
|
||||
db.get("pad:"+sourceID+":revs:"+i, function (err, rev) {
|
||||
//console.log("HERE");
|
||||
|
||||
if (ERR(err, callback)) return;
|
||||
db.set("pad:"+destinationID+":revs:"+i, rev);
|
||||
});
|
||||
}
|
||||
|
||||
callback();
|
||||
},
|
||||
//add the new pad to all authors who contributed to the old one
|
||||
function(callback)
|
||||
{
|
||||
var authorIDs = _this.getAllAuthors();
|
||||
|
||||
authorIDs.forEach(function (authorID)
|
||||
{
|
||||
console.log("authors");
|
||||
authorManager.addPad(authorID, destinationID);
|
||||
});
|
||||
|
||||
callback();
|
||||
},
|
||||
// parallel
|
||||
], callback);
|
||||
},
|
||||
// series
|
||||
], function(err)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
callback(null, {padID: destinationID});
|
||||
});
|
||||
};
|
||||
|
||||
Pad.prototype.remove = function remove(callback) {
|
||||
var padID = this.id;
|
||||
var _this = this;
|
||||
|
@ -487,7 +635,7 @@ Pad.prototype.remove = function remove(callback) {
|
|||
|
||||
authorIDs.forEach(function (authorID)
|
||||
{
|
||||
authorManager.removePad(authorID, padID);
|
||||
authorManager.removePad(authorID, padID);
|
||||
});
|
||||
|
||||
callback();
|
||||
|
|
|
@ -24,7 +24,9 @@ var Pad = require("../db/Pad").Pad;
|
|||
var db = require("./DB").db;
|
||||
|
||||
/**
|
||||
* An Object containing all known Pads. Provides "get" and "set" functions,
|
||||
* A cache of all loaded Pads.
|
||||
*
|
||||
* Provides "get" and "set" functions,
|
||||
* which should be used instead of indexing with brackets. These prepend a
|
||||
* colon to the key, to avoid conflicting with built-in Object methods or with
|
||||
* these functions themselves.
|
||||
|
@ -37,39 +39,55 @@ var globalPads = {
|
|||
set: function (name, value)
|
||||
{
|
||||
this[':'+name] = value;
|
||||
padList.addPad(name);
|
||||
},
|
||||
remove: function (name) { delete this[':'+name]; }
|
||||
remove: function (name) {
|
||||
delete this[':'+name];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A cache of the list of all pads.
|
||||
*
|
||||
* Updated without db access as new pads are created/old ones removed.
|
||||
*/
|
||||
var padList = {
|
||||
list: [],
|
||||
sorted : false,
|
||||
init: function()
|
||||
initiated: false,
|
||||
init: function(cb)
|
||||
{
|
||||
db.findKeys("pad:*", "*:*:*", function(err, dbData)
|
||||
{
|
||||
if(ERR(err)) return;
|
||||
if(ERR(err, cb)) return;
|
||||
if(dbData != null){
|
||||
padList.initiated = true
|
||||
dbData.forEach(function(val){
|
||||
padList.addPad(val.replace(/pad:/,""),false);
|
||||
});
|
||||
cb && cb()
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
load: function(cb) {
|
||||
if(this.initiated) cb && cb()
|
||||
else this.init(cb)
|
||||
},
|
||||
/**
|
||||
* Returns all pads in alphabetical order as array.
|
||||
*/
|
||||
getPads: function(){
|
||||
if(!this.sorted){
|
||||
this.list=this.list.sort();
|
||||
this.sorted=true;
|
||||
}
|
||||
return this.list;
|
||||
getPads: function(cb){
|
||||
this.load(function() {
|
||||
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){
|
||||
this.list.push(name);
|
||||
this.sorted=false;
|
||||
|
@ -77,7 +95,8 @@ var padList = {
|
|||
},
|
||||
removePad: function(name)
|
||||
{
|
||||
var index=this.list.indexOf(name);
|
||||
if(!this.initiated) return;
|
||||
var index = this.list.indexOf(name);
|
||||
if(index>-1){
|
||||
this.list.splice(index,1);
|
||||
this.sorted=false;
|
||||
|
@ -85,7 +104,6 @@ var padList = {
|
|||
}
|
||||
};
|
||||
//initialises the allknowing data structure
|
||||
padList.init();
|
||||
|
||||
/**
|
||||
* An array of padId transformations. These represent changes in pad name policy over
|
||||
|
@ -146,25 +164,23 @@ exports.getPad = function(id, text, callback)
|
|||
else
|
||||
{
|
||||
pad = new Pad(id);
|
||||
|
||||
|
||||
//initalize the pad
|
||||
pad.init(text, function(err)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
globalPads.set(id, pad);
|
||||
padList.addPad(id);
|
||||
callback(null, pad);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
exports.listAllPads = function(callback)
|
||||
exports.listAllPads = function(cb)
|
||||
{
|
||||
if(callback != null){
|
||||
callback(null,{padIDs: padList.getPads()});
|
||||
}else{
|
||||
return {padIDs: padList.getPads()};
|
||||
}
|
||||
padList.getPads(function(list) {
|
||||
cb && cb(null, {padIDs: list});
|
||||
});
|
||||
}
|
||||
|
||||
//checks if a pad exists
|
||||
|
@ -230,9 +246,8 @@ exports.removePad = function(padId){
|
|||
padList.removePad(padId);
|
||||
}
|
||||
|
||||
//removes a pad from the array
|
||||
//removes a pad from the cache
|
||||
exports.unloadPad = function(padId)
|
||||
{
|
||||
if(globalPads.get(padId))
|
||||
globalPads.remove(padId);
|
||||
globalPads.remove(padId);
|
||||
}
|
||||
|
|
|
@ -77,28 +77,22 @@ exports.getPadId = function(readOnlyId, callback)
|
|||
* returns a the padId and readonlyPadId in an object for any id
|
||||
* @param {String} padIdOrReadonlyPadId read only id or real pad id
|
||||
*/
|
||||
exports.getIds = function(padIdOrReadonlyPadId, callback) {
|
||||
var handleRealPadId = function () {
|
||||
exports.getReadOnlyId(padIdOrReadonlyPadId, function (err, value) {
|
||||
exports.getIds = function(id, callback) {
|
||||
if (id.indexOf("r.") == 0)
|
||||
exports.getPadId(id, function (err, value) {
|
||||
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
|
||||
exports.getReadOnlyId(id, function (err, value) {
|
||||
callback(null, {
|
||||
readOnlyPadId: value,
|
||||
padId: padIdOrReadonlyPadId,
|
||||
padId: id,
|
||||
readonly: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (padIdOrReadonlyPadId.indexOf("r.") != 0)
|
||||
return handleRealPadId();
|
||||
|
||||
exports.getPadId(padIdOrReadonlyPadId, function (err, value) {
|
||||
if(ERR(err, callback)) return;
|
||||
if (value == null)
|
||||
return handleRealPadId();
|
||||
callback(null, {
|
||||
readOnlyPadId: padIdOrReadonlyPadId,
|
||||
padId: value,
|
||||
readonly: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ var padManager = require("./PadManager");
|
|||
var sessionManager = require("./SessionManager");
|
||||
var settings = require("../utils/Settings");
|
||||
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
||||
var log4js = require('log4js');
|
||||
var authLogger = log4js.getLogger("auth");
|
||||
|
||||
/**
|
||||
* This function controlls the access to a pad, it checks if the user can access a pad.
|
||||
|
@ -39,6 +41,11 @@ var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
|||
exports.checkAccess = function (padID, sessionCookie, token, password, callback)
|
||||
{
|
||||
var statusObject;
|
||||
|
||||
if(!padID) {
|
||||
callback(null, {accessStatus: "deny"});
|
||||
return;
|
||||
}
|
||||
|
||||
// a valid session is required (api-only mode)
|
||||
if(settings.requireSession)
|
||||
|
@ -117,31 +124,43 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
|
|||
//get information about all sessions contained in this cookie
|
||||
function(callback)
|
||||
{
|
||||
if (!sessionCookie) {
|
||||
if (!sessionCookie)
|
||||
{
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
var sessionIDs = sessionCookie.split(',');
|
||||
async.forEach(sessionIDs, function(sessionID, callback) {
|
||||
sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) {
|
||||
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") return;
|
||||
if(err && err.message == "sessionID does not exist")
|
||||
{
|
||||
authLogger.debug("Auth failed: unknown session");
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
var now = Math.floor(new Date().getTime()/1000);
|
||||
|
||||
//is it for this group?
|
||||
if(sessionInfo.groupID != groupID) {
|
||||
callback();
|
||||
return;
|
||||
if(sessionInfo.groupID != groupID)
|
||||
{
|
||||
authLogger.debug("Auth failed: wrong group");
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
//is validUntil still ok?
|
||||
if(sessionInfo.validUntil <= now){
|
||||
callback();
|
||||
return;
|
||||
if(sessionInfo.validUntil <= now)
|
||||
{
|
||||
authLogger.debug("Auth failed: validUntil");
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// There is a valid session
|
||||
|
@ -234,7 +253,11 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
|
|||
//--> grant access
|
||||
statusObject = {accessStatus: "grant", authorID: sessionAuthor};
|
||||
//--> deny access if user isn't allowed to create the pad
|
||||
if(settings.editOnly) statusObject.accessStatus = "deny";
|
||||
if(settings.editOnly)
|
||||
{
|
||||
authLogger.debug("Auth failed: valid session & pad does not exist");
|
||||
statusObject.accessStatus = "deny";
|
||||
}
|
||||
}
|
||||
// there is no valid session avaiable AND pad exists
|
||||
else if(!validSession && padExists)
|
||||
|
@ -266,6 +289,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
|
|||
//- its not public
|
||||
else if(!isPublic)
|
||||
{
|
||||
authLogger.debug("Auth failed: invalid session & pad is not public");
|
||||
//--> deny access
|
||||
statusObject = {accessStatus: "deny"};
|
||||
}
|
||||
|
@ -277,6 +301,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
|
|||
// there is no valid session avaiable AND pad doesn't exists
|
||||
else
|
||||
{
|
||||
authLogger.debug("Auth failed: invalid session & pad does not exist");
|
||||
//--> deny access
|
||||
statusObject = {accessStatus: "deny"};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* The Session Manager provides functions to manage session in the database
|
||||
* The Session Manager provides functions to manage session in the database, it only provides session management for sessions created by the API
|
||||
*/
|
||||
|
||||
/*
|
||||
|
|
82
src/node/db/SessionStore.js
Normal file
82
src/node/db/SessionStore.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
var Store = require('ep_etherpad-lite/node_modules/connect/lib/middleware/session/store'),
|
||||
utils = require('ep_etherpad-lite/node_modules/connect/lib/utils'),
|
||||
Session = require('ep_etherpad-lite/node_modules/connect/lib/middleware/session/session'),
|
||||
db = require('ep_etherpad-lite/node/db/DB').db,
|
||||
log4js = require('ep_etherpad-lite/node_modules/log4js'),
|
||||
messageLogger = log4js.getLogger("SessionStore");
|
||||
|
||||
var SessionStore = module.exports = function SessionStore() {};
|
||||
|
||||
SessionStore.prototype.__proto__ = Store.prototype;
|
||||
|
||||
SessionStore.prototype.get = function(sid, fn){
|
||||
messageLogger.debug('GET ' + sid);
|
||||
var self = this;
|
||||
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) {
|
||||
fn(null, sess);
|
||||
} else {
|
||||
self.destroy(sid, fn);
|
||||
}
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
SessionStore.prototype.set = function(sid, sess, fn){
|
||||
messageLogger.debug('SET ' + sid);
|
||||
db.set("sessionstorage:" + sid, sess);
|
||||
process.nextTick(function(){
|
||||
if(fn) fn();
|
||||
});
|
||||
};
|
||||
|
||||
SessionStore.prototype.destroy = function(sid, fn){
|
||||
messageLogger.debug('DESTROY ' + sid);
|
||||
db.remove("sessionstorage:" + sid);
|
||||
process.nextTick(function(){
|
||||
if(fn) fn();
|
||||
});
|
||||
};
|
||||
|
||||
SessionStore.prototype.all = function(fn){
|
||||
messageLogger.debug('ALL');
|
||||
var sessions = [];
|
||||
db.forEach(function(key, value){
|
||||
if (key.substr(0,15) === "sessionstorage:") {
|
||||
sessions.push(value);
|
||||
}
|
||||
});
|
||||
fn(null, sessions);
|
||||
};
|
||||
|
||||
SessionStore.prototype.clear = function(fn){
|
||||
messageLogger.debug('CLEAR');
|
||||
db.forEach(function(key, value){
|
||||
if (key.substr(0,15) === "sessionstorage:") {
|
||||
db.db.remove("session:" + key);
|
||||
}
|
||||
});
|
||||
if(fn) fn();
|
||||
};
|
||||
|
||||
SessionStore.prototype.length = function(fn){
|
||||
messageLogger.debug('LENGTH');
|
||||
var i = 0;
|
||||
db.forEach(function(key, value){
|
||||
if (key.substr(0,15) === "sessionstorage:") {
|
||||
i++;
|
||||
}
|
||||
});
|
||||
fn(null, i);
|
||||
};
|
|
@ -31,7 +31,7 @@ try
|
|||
{
|
||||
apikey = fs.readFileSync("./APIKEY.txt","utf8");
|
||||
}
|
||||
catch(e)
|
||||
catch(e)
|
||||
{
|
||||
apikey = randomString(32);
|
||||
fs.writeFileSync("./APIKEY.txt",apikey,"utf8");
|
||||
|
@ -180,7 +180,7 @@ var version =
|
|||
, "deleteGroup" : ["groupID"]
|
||||
, "listPads" : ["groupID"]
|
||||
, "listAllPads" : []
|
||||
, "createDiffHTML" : ["padID", "startRev", "endRev"]
|
||||
, "createDiffHTML" : ["padID", "startRev", "endRev"]
|
||||
, "createPad" : ["padID", "text"]
|
||||
, "createGroupPad" : ["groupID", "padName", "text"]
|
||||
, "createAuthor" : ["name"]
|
||||
|
@ -214,8 +214,100 @@ var version =
|
|||
, "getChatHistory" : ["padID", "start", "end"]
|
||||
, "getChatHead" : ["padID"]
|
||||
}
|
||||
, "1.2.8":
|
||||
{ "createGroup" : []
|
||||
, "createGroupIfNotExistsFor" : ["groupMapper"]
|
||||
, "deleteGroup" : ["groupID"]
|
||||
, "listPads" : ["groupID"]
|
||||
, "listAllPads" : []
|
||||
, "createDiffHTML" : ["padID", "startRev", "endRev"]
|
||||
, "createPad" : ["padID", "text"]
|
||||
, "createGroupPad" : ["groupID", "padName", "text"]
|
||||
, "createAuthor" : ["name"]
|
||||
, "createAuthorIfNotExistsFor": ["authorMapper" , "name"]
|
||||
, "listPadsOfAuthor" : ["authorID"]
|
||||
, "createSession" : ["groupID", "authorID", "validUntil"]
|
||||
, "deleteSession" : ["sessionID"]
|
||||
, "getSessionInfo" : ["sessionID"]
|
||||
, "listSessionsOfGroup" : ["groupID"]
|
||||
, "listSessionsOfAuthor" : ["authorID"]
|
||||
, "getText" : ["padID", "rev"]
|
||||
, "setText" : ["padID", "text"]
|
||||
, "getHTML" : ["padID", "rev"]
|
||||
, "setHTML" : ["padID", "html"]
|
||||
, "getAttributePool" : ["padID"]
|
||||
, "getRevisionsCount" : ["padID"]
|
||||
, "getRevisionChangeset" : ["padID", "rev"]
|
||||
, "getLastEdited" : ["padID"]
|
||||
, "deletePad" : ["padID"]
|
||||
, "getReadOnlyID" : ["padID"]
|
||||
, "setPublicStatus" : ["padID", "publicStatus"]
|
||||
, "getPublicStatus" : ["padID"]
|
||||
, "setPassword" : ["padID", "password"]
|
||||
, "isPasswordProtected" : ["padID"]
|
||||
, "listAuthorsOfPad" : ["padID"]
|
||||
, "padUsersCount" : ["padID"]
|
||||
, "getAuthorName" : ["authorID"]
|
||||
, "padUsers" : ["padID"]
|
||||
, "sendClientsMessage" : ["padID", "msg"]
|
||||
, "listAllGroups" : []
|
||||
, "checkToken" : []
|
||||
, "getChatHistory" : ["padID"]
|
||||
, "getChatHistory" : ["padID", "start", "end"]
|
||||
, "getChatHead" : ["padID"]
|
||||
}
|
||||
, "1.2.9":
|
||||
{ "createGroup" : []
|
||||
, "createGroupIfNotExistsFor" : ["groupMapper"]
|
||||
, "deleteGroup" : ["groupID"]
|
||||
, "listPads" : ["groupID"]
|
||||
, "listAllPads" : []
|
||||
, "createDiffHTML" : ["padID", "startRev", "endRev"]
|
||||
, "createPad" : ["padID", "text"]
|
||||
, "createGroupPad" : ["groupID", "padName", "text"]
|
||||
, "createAuthor" : ["name"]
|
||||
, "createAuthorIfNotExistsFor": ["authorMapper" , "name"]
|
||||
, "listPadsOfAuthor" : ["authorID"]
|
||||
, "createSession" : ["groupID", "authorID", "validUntil"]
|
||||
, "deleteSession" : ["sessionID"]
|
||||
, "getSessionInfo" : ["sessionID"]
|
||||
, "listSessionsOfGroup" : ["groupID"]
|
||||
, "listSessionsOfAuthor" : ["authorID"]
|
||||
, "getText" : ["padID", "rev"]
|
||||
, "setText" : ["padID", "text"]
|
||||
, "getHTML" : ["padID", "rev"]
|
||||
, "setHTML" : ["padID", "html"]
|
||||
, "getAttributePool" : ["padID"]
|
||||
, "getRevisionsCount" : ["padID"]
|
||||
, "getRevisionChangeset" : ["padID", "rev"]
|
||||
, "getLastEdited" : ["padID"]
|
||||
, "deletePad" : ["padID"]
|
||||
, "copyPad" : ["sourceID", "destinationID", "force"]
|
||||
, "movePad" : ["sourceID", "destinationID", "force"]
|
||||
, "getReadOnlyID" : ["padID"]
|
||||
, "setPublicStatus" : ["padID", "publicStatus"]
|
||||
, "getPublicStatus" : ["padID"]
|
||||
, "setPassword" : ["padID", "password"]
|
||||
, "isPasswordProtected" : ["padID"]
|
||||
, "listAuthorsOfPad" : ["padID"]
|
||||
, "padUsersCount" : ["padID"]
|
||||
, "getAuthorName" : ["authorID"]
|
||||
, "padUsers" : ["padID"]
|
||||
, "sendClientsMessage" : ["padID", "msg"]
|
||||
, "listAllGroups" : []
|
||||
, "checkToken" : []
|
||||
, "getChatHistory" : ["padID"]
|
||||
, "getChatHistory" : ["padID", "start", "end"]
|
||||
, "getChatHead" : ["padID"]
|
||||
}
|
||||
};
|
||||
|
||||
// set the latest available API version here
|
||||
exports.latestApiVersion = '1.2.9';
|
||||
|
||||
// exports the versions so it can be used by the new Swagger endpoint
|
||||
exports.version = version;
|
||||
|
||||
/**
|
||||
* Handles a HTTP API call
|
||||
* @param functionName the name of the called function
|
||||
|
@ -235,7 +327,7 @@ exports.handle = function(apiVersion, functionName, fields, req, res)
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//say goodbye if this is an unkown API version
|
||||
if(!isKnownApiVersion)
|
||||
{
|
||||
|
@ -243,7 +335,7 @@ exports.handle = function(apiVersion, functionName, fields, req, res)
|
|||
res.send({code: 3, message: "no such api version", data: null});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//check if this is a valid function name
|
||||
var isKnownFunctionname = false;
|
||||
for(var knownFunctionname in version[apiVersion])
|
||||
|
@ -254,15 +346,17 @@ exports.handle = function(apiVersion, functionName, fields, req, res)
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//say goodbye if this is a unkown function
|
||||
if(!isKnownFunctionname)
|
||||
{
|
||||
res.send({code: 3, message: "no such function", data: null});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//check the api key!
|
||||
fields["apikey"] = fields["apikey"] || fields["api_key"];
|
||||
|
||||
if(fields["apikey"] != apikey.trim())
|
||||
{
|
||||
res.send({code: 4, message: "no or wrong API Key", data: null});
|
||||
|
@ -296,21 +390,19 @@ exports.handle = function(apiVersion, functionName, fields, req, res)
|
|||
function callAPI(apiVersion, functionName, fields, req, res)
|
||||
{
|
||||
//put the function parameters in an array
|
||||
var functionParams = [];
|
||||
for(var i=0;i<version[apiVersion][functionName].length;i++)
|
||||
{
|
||||
functionParams.push(fields[ version[apiVersion][functionName][i] ]);
|
||||
}
|
||||
|
||||
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)
|
||||
data = null;
|
||||
|
||||
|
||||
res.send({code: 0, message: "ok", data: data});
|
||||
}
|
||||
// parameters were wrong and the api stopped execution, pass the error
|
||||
|
@ -325,7 +417,7 @@ function callAPI(apiVersion, functionName, fields, req, res)
|
|||
ERR(err);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//call the api function
|
||||
api[functionName](functionParams[0],functionParams[1],functionParams[2],functionParams[3],functionParams[4]);
|
||||
api[functionName].apply(this, functionParams);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
var ERR = require("async-stacktrace");
|
||||
var exporthtml = require("../utils/ExportHtml");
|
||||
var exporttxt = require("../utils/ExportTxt");
|
||||
var exportdokuwiki = require("../utils/ExportDokuWiki");
|
||||
var padManager = require("../db/PadManager");
|
||||
var async = require("async");
|
||||
|
@ -48,22 +49,75 @@ exports.doExport = function(req, res, padId, type)
|
|||
res.attachment(padId + "." + type);
|
||||
|
||||
//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 == "txt")
|
||||
{
|
||||
padManager.getPad(padId, function(err, pad)
|
||||
{
|
||||
ERR(err);
|
||||
if(req.params.rev){
|
||||
pad.getInternalRevisionAText(req.params.rev, function(junk, text)
|
||||
{
|
||||
res.send(text.text ? text.text : null);
|
||||
});
|
||||
}
|
||||
else
|
||||
var txt;
|
||||
var randNum;
|
||||
var srcFile, destFile;
|
||||
|
||||
async.series([
|
||||
//render the txt document
|
||||
function(callback)
|
||||
{
|
||||
res.send(pad.text());
|
||||
exporttxt.getPadTXTDocument(padId, req.params.rev, false, function(err, _txt)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
txt = _txt;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
//decide what to do with the txt export
|
||||
function(callback)
|
||||
{
|
||||
//if this is a txt export, we can send this from here directly
|
||||
res.send(txt);
|
||||
callback("stop");
|
||||
},
|
||||
//send the convert job to abiword
|
||||
function(callback)
|
||||
{
|
||||
//ensure html can be collected by the garbage collector
|
||||
txt = null;
|
||||
|
||||
destFile = tempDirectory + "/eplite_export_" + randNum + "." + type;
|
||||
abiword.convertFile(srcFile, destFile, type, callback);
|
||||
},
|
||||
//send the file
|
||||
function(callback)
|
||||
{
|
||||
res.sendfile(destFile, null, callback);
|
||||
},
|
||||
//clean up temporary files
|
||||
function(callback)
|
||||
{
|
||||
async.parallel([
|
||||
function(callback)
|
||||
{
|
||||
fs.unlink(srcFile, callback);
|
||||
},
|
||||
function(callback)
|
||||
{
|
||||
//100ms delay to accomidate for slow windows fs
|
||||
if(os.type().indexOf("Windows") > -1)
|
||||
{
|
||||
setTimeout(function()
|
||||
{
|
||||
fs.unlink(destFile, callback);
|
||||
}, 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
fs.unlink(destFile, callback);
|
||||
}
|
||||
}
|
||||
], callback);
|
||||
}
|
||||
});
|
||||
], function(err)
|
||||
{
|
||||
if(err && err != "stop") ERR(err);
|
||||
})
|
||||
}
|
||||
else if(type == 'dokuwiki')
|
||||
{
|
||||
|
|
|
@ -28,7 +28,9 @@ var ERR = require("async-stacktrace")
|
|||
, settings = require('../utils/Settings')
|
||||
, formidable = require('formidable')
|
||||
, os = require("os")
|
||||
, importHtml = require("../utils/ImportHtml");
|
||||
, importHtml = require("../utils/ImportHtml")
|
||||
, log4js = require("log4js")
|
||||
, hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
|
||||
|
||||
//load abiword only if its enabled
|
||||
if(settings.abiword != null)
|
||||
|
@ -42,13 +44,18 @@ var tmpDirectory = process.env.TEMP || process.env.TMPDIR || process.env.TMP ||
|
|||
*/
|
||||
exports.doImport = function(req, res, padId)
|
||||
{
|
||||
var apiLogger = log4js.getLogger("ImportHandler");
|
||||
|
||||
//pipe to a file
|
||||
//convert file to html via abiword
|
||||
//set html in the pad
|
||||
|
||||
var srcFile, destFile
|
||||
, pad
|
||||
, text;
|
||||
, text
|
||||
, importHandledByPlugin;
|
||||
|
||||
var randNum = Math.floor(Math.random()*0xFFFFFFFF);
|
||||
|
||||
async.series([
|
||||
//save the uploaded file to /tmp
|
||||
|
@ -60,7 +67,7 @@ exports.doImport = function(req, res, padId)
|
|||
form.parse(req, function(err, fields, files) {
|
||||
//the upload failed, stop at this point
|
||||
if(err || files.file === undefined) {
|
||||
console.warn("Uploading Error: " + err.stack);
|
||||
if(err) console.warn("Uploading Error: " + err.stack);
|
||||
callback("uploadFailed");
|
||||
}
|
||||
//everything ok, continue
|
||||
|
@ -87,32 +94,69 @@ exports.doImport = function(req, res, padId)
|
|||
else {
|
||||
var oldSrcFile = srcFile;
|
||||
srcFile = path.join(path.dirname(srcFile),path.basename(srcFile, fileEnding)+".txt");
|
||||
|
||||
fs.rename(oldSrcFile, srcFile, callback);
|
||||
}
|
||||
},
|
||||
|
||||
//convert file to html
|
||||
function(callback) {
|
||||
var randNum = Math.floor(Math.random()*0xFFFFFFFF);
|
||||
function(callback){
|
||||
destFile = path.join(tmpDirectory, "eplite_import_" + randNum + ".htm");
|
||||
|
||||
if (abiword) {
|
||||
abiword.convertFile(srcFile, destFile, "htm", function(err) {
|
||||
//catch convert errors
|
||||
if(err) {
|
||||
console.warn("Converting Error:", err);
|
||||
return callback("convertFailed");
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// if no abiword only rename
|
||||
fs.rename(srcFile, destFile, callback);
|
||||
// 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..
|
||||
importHandledByPlugin = true;
|
||||
callback();
|
||||
}else{
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
//convert file to html
|
||||
function(callback) {
|
||||
if(!importHandledByPlugin){
|
||||
if (abiword) {
|
||||
abiword.convertFile(srcFile, destFile, "htm", function(err) {
|
||||
//catch convert errors
|
||||
if(err) {
|
||||
console.warn("Converting Error:", err);
|
||||
return callback("convertFailed");
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// if no abiword only rename
|
||||
fs.rename(srcFile, destFile, callback);
|
||||
}
|
||||
}else{
|
||||
callback();
|
||||
}
|
||||
},
|
||||
|
||||
function(callback) {
|
||||
if (!abiword) {
|
||||
// Read the file with no encoding for raw buffer access.
|
||||
fs.readFile(destFile, function(err, buf) {
|
||||
if (err) throw err;
|
||||
var isAscii = true;
|
||||
// Check if there are only ascii chars in the uploaded file
|
||||
for (var i=0, len=buf.length; i<len; i++) {
|
||||
if (buf[i] > 240) {
|
||||
isAscii=false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isAscii) {
|
||||
callback();
|
||||
} else {
|
||||
callback("uploadFailed");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
|
||||
//get the pad object
|
||||
function(callback) {
|
||||
padManager.getPad(padId, function(err, _pad){
|
||||
|
@ -127,7 +171,10 @@ exports.doImport = function(req, res, padId)
|
|||
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){
|
||||
|
@ -142,7 +189,11 @@ exports.doImport = function(req, res, padId)
|
|||
function(callback) {
|
||||
var fileEnding = path.extname(srcFile).toLowerCase();
|
||||
if (abiword || fileEnding == ".htm" || fileEnding == ".html") {
|
||||
importHtml.setPadHTML(pad, text);
|
||||
try{
|
||||
importHtml.setPadHTML(pad, text);
|
||||
}catch(e){
|
||||
apiLogger.warn("Error importing, possibly caused by malformed HTML");
|
||||
}
|
||||
} else {
|
||||
pad.setText(text);
|
||||
}
|
||||
|
@ -176,7 +227,7 @@ exports.doImport = function(req, res, padId)
|
|||
ERR(err);
|
||||
|
||||
//close the connection
|
||||
res.send("<head><script type='text/javascript' src='../../static/js/jquery.js'></script></head><script>$(window).load(function(){if ( (!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf(\"1.8.\") == 0)) ){document.domain = document.domain;}var impexp = window.parent.padimpexp.handleFrameCall('" + status + "');})</script>", 200);
|
||||
res.send("<head><script type='text/javascript' src='../../static/js/jquery.js'></script><script type='text/javascript' src='../../static/js/jquery_browser.js'></script></head><script>$(window).load(function(){if ( (!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf(\"1.8.\") == 0)) ){document.domain = document.domain;}var impexp = window.parent.padimpexp.handleFrameCall('" + status + "');})</script>", 200);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@ var messageLogger = log4js.getLogger("message");
|
|||
var accessLogger = log4js.getLogger("access");
|
||||
var _ = require('underscore');
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
|
||||
var channels = require("channels");
|
||||
var stats = require('../stats');
|
||||
|
||||
/**
|
||||
* A associative array that saves informations about a session
|
||||
|
@ -47,6 +49,17 @@ var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
|
|||
* author = the author name of this session
|
||||
*/
|
||||
var sessioninfos = {};
|
||||
exports.sessioninfos = sessioninfos;
|
||||
|
||||
// Measure total amount of users
|
||||
stats.gauge('totalUsers', function() {
|
||||
return Object.keys(socketio.sockets.sockets).length
|
||||
})
|
||||
|
||||
/**
|
||||
* A changeset queue per pad that is processed by handleUserChanges()
|
||||
*/
|
||||
var padChannels = new channels.channels(handleUserChanges);
|
||||
|
||||
/**
|
||||
* Saves the Socket class we need to send and recieve data from the client
|
||||
|
@ -67,7 +80,9 @@ exports.setSocketIO = function(socket_io)
|
|||
* @param client the new client
|
||||
*/
|
||||
exports.handleConnect = function(client)
|
||||
{
|
||||
{
|
||||
stats.meter('connects').mark();
|
||||
|
||||
//Initalize sessioninfos for this new session
|
||||
sessioninfos[client.id]={};
|
||||
}
|
||||
|
@ -92,12 +107,23 @@ exports.kickSessionsFromPad = function(padID)
|
|||
*/
|
||||
exports.handleDisconnect = function(client)
|
||||
{
|
||||
stats.meter('disconnects').mark();
|
||||
|
||||
//save the padname of this session
|
||||
var session = sessioninfos[client.id];
|
||||
|
||||
//if this connection was already etablished with a handshake, send a disconnect message to the others
|
||||
if(session && session.author)
|
||||
{
|
||||
client.get('remoteAddress', function(er, ip) {
|
||||
//Anonymize the IP address if IP logging is disabled
|
||||
if(settings.disableIPlogging) {
|
||||
ip = 'ANONYMOUS';
|
||||
}
|
||||
|
||||
accessLogger.info('[LEAVE] Pad "'+session.padId+'": Author "'+session.author+'" on client '+client.id+' with IP "'+ip+'" left the pad')
|
||||
})
|
||||
|
||||
//get the author color out of the db
|
||||
authorManager.getAuthorColorId(session.author, function(err, color)
|
||||
{
|
||||
|
@ -122,10 +148,6 @@ exports.handleDisconnect = function(client)
|
|||
});
|
||||
}
|
||||
|
||||
client.get('remoteAddress', function(er, ip) {
|
||||
accessLogger.info('[LEAVE] Pad "'+session.padId+'": Author "'+session.author+'" on client '+client.id+' with IP "'+ip+'" left the pad')
|
||||
})
|
||||
|
||||
//Delete the sessioninfos entrys of this session
|
||||
delete sessioninfos[client.id];
|
||||
}
|
||||
|
@ -137,26 +159,25 @@ exports.handleDisconnect = function(client)
|
|||
*/
|
||||
exports.handleMessage = function(client, message)
|
||||
{
|
||||
|
||||
if(message == null)
|
||||
{
|
||||
messageLogger.warn("Message is null!");
|
||||
return;
|
||||
}
|
||||
if(!message.type)
|
||||
{
|
||||
messageLogger.warn("Message has no type attribute!");
|
||||
return;
|
||||
}
|
||||
if(!sessioninfos[client.id]) {
|
||||
messageLogger.warn("Dropped message from an unknown connection.")
|
||||
return;
|
||||
}
|
||||
|
||||
var handleMessageHook = function(callback){
|
||||
var dropMessage = false;
|
||||
|
||||
// Call handleMessage hook. If a plugin returns null, the message will be dropped. Note that for all messages
|
||||
// handleMessage will be called, even if the client is not authorized
|
||||
hooks.aCallAll("handleMessage", { client: client, message: message }, function ( err, messages ) {
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
_.each(messages, function(newMessage){
|
||||
if ( newMessage === null ) {
|
||||
dropMessage = true;
|
||||
|
@ -178,7 +199,8 @@ exports.handleMessage = function(client, message)
|
|||
if (sessioninfos[client.id].readonly) {
|
||||
messageLogger.warn("Dropped message, COLLABROOM for readonly pad");
|
||||
} else if (message.data.type == "USER_CHANGES") {
|
||||
handleUserChanges(client, message);
|
||||
stats.counter('pendingEdits').inc()
|
||||
padChannels.emit(message.padId, {client: client, message: message});// add to pad queue
|
||||
} else if (message.data.type == "USERINFO_UPDATE") {
|
||||
handleUserInfoUpdate(client, message);
|
||||
} else if (message.data.type == "CHAT_MESSAGE") {
|
||||
|
@ -205,31 +227,61 @@ exports.handleMessage = function(client, message)
|
|||
//check permissions
|
||||
function(callback)
|
||||
{
|
||||
|
||||
// If the message has a padId we assume the client is already known to the server and needs no re-authorization
|
||||
if(!message.padId)
|
||||
return callback();
|
||||
// client tried to auth for the first time (first msg from the client)
|
||||
if(message.type == "CLIENT_READY") {
|
||||
// Remember this information since we won't
|
||||
// have the cookie in further socket.io messages.
|
||||
// This information will be used to check if
|
||||
// the sessionId of this connection is still valid
|
||||
// since it could have been deleted by the API.
|
||||
sessioninfos[client.id].auth =
|
||||
{
|
||||
sessionID: message.sessionID,
|
||||
padID: message.padId,
|
||||
token : message.token,
|
||||
password: message.password
|
||||
};
|
||||
}
|
||||
|
||||
// Note: message.sessionID is an entirely different kind of
|
||||
// session from the sessions we use here! Beware! FIXME: Call
|
||||
// our "sessions" "connections".
|
||||
// session from the sessions we use here! Beware!
|
||||
// FIXME: Call our "sessions" "connections".
|
||||
// FIXME: Use a hook instead
|
||||
// FIXME: Allow to override readwrite access with readonly
|
||||
securityManager.checkAccess(message.padId, message.sessionID, message.token, message.password, function(err, statusObject)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
//access was granted
|
||||
if(statusObject.accessStatus == "grant")
|
||||
// FIXME: A message might arrive but wont have an auth object, this is obviously bad so we should deny it
|
||||
// Simulate using the load testing tool
|
||||
if(!sessioninfos[client.id].auth){
|
||||
console.error("Auth was never applied to a session. If you are using the stress-test tool then restart Etherpad and the Stress test tool.")
|
||||
callback();
|
||||
}else{
|
||||
var auth = sessioninfos[client.id].auth;
|
||||
var checkAccessCallback = function(err, statusObject)
|
||||
{
|
||||
callback();
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
//access was granted
|
||||
if(statusObject.accessStatus == "grant")
|
||||
{
|
||||
callback();
|
||||
}
|
||||
//no access, send the client a message that tell him why
|
||||
else
|
||||
{
|
||||
client.json.send({accessStatus: statusObject.accessStatus})
|
||||
}
|
||||
};
|
||||
//check if pad is requested via readOnly
|
||||
if (auth.padID.indexOf("r.") === 0) {
|
||||
//Pad is readOnly, first get the real Pad ID
|
||||
readOnlyManager.getPadId(auth.padID, function(err, value) {
|
||||
ERR(err);
|
||||
securityManager.checkAccess(value, auth.sessionID, auth.token, auth.password, checkAccessCallback);
|
||||
});
|
||||
} else {
|
||||
securityManager.checkAccess(auth.padID, auth.sessionID, auth.token, auth.password, checkAccessCallback);
|
||||
}
|
||||
//no access, send the client a message that tell him why
|
||||
else
|
||||
{
|
||||
client.json.send({accessStatus: statusObject.accessStatus})
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
finalHandler
|
||||
]);
|
||||
|
@ -254,6 +306,25 @@ function handleSaveRevisionMessage(client, message){
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a custom message, different to the function below as it handles objects not strings and you can
|
||||
* direct the message to specific sessionID
|
||||
*
|
||||
* @param msg {Object} the message we're sending
|
||||
* @param sessionID {string} the socketIO session to which we're sending this message
|
||||
*/
|
||||
exports.handleCustomObjectMessage = function (msg, sessionID, cb) {
|
||||
if(msg.data.type === "CUSTOM"){
|
||||
if(sessionID){ // If a sessionID is targeted then send directly to this sessionID
|
||||
socketio.sockets.socket(sessionID).json.send(msg); // send a targeted message
|
||||
}else{
|
||||
socketio.sockets.in(msg.data.payload.padId).json.send(msg); // broadcast to all clients on this pad
|
||||
}
|
||||
}
|
||||
cb(null, {});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles a custom message (sent via HTTP API request)
|
||||
*
|
||||
|
@ -384,7 +455,7 @@ function handleGetChatMessages(client, message)
|
|||
pad.getChatMessages(start, end, function(err, chatMessages)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
|
||||
var infoMsg = {
|
||||
type: "COLLABROOM",
|
||||
data: {
|
||||
|
@ -392,7 +463,7 @@ function handleGetChatMessages(client, message)
|
|||
messages: chatMessages
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// send the messages back to the client
|
||||
client.json.send(infoMsg);
|
||||
});
|
||||
|
@ -493,23 +564,29 @@ function handleUserInfoUpdate(client, message)
|
|||
* @param client the client that send this message
|
||||
* @param message the message from the client
|
||||
*/
|
||||
function handleUserChanges(client, message)
|
||||
function handleUserChanges(data, cb)
|
||||
{
|
||||
var client = data.client
|
||||
, message = data.message
|
||||
|
||||
// This one's no longer pending, as we're gonna process it now
|
||||
stats.counter('pendingEdits').dec()
|
||||
|
||||
// Make sure all required fields are present
|
||||
if(message.data.baseRev == null)
|
||||
{
|
||||
messageLogger.warn("Dropped message, USER_CHANGES Message has no baseRev!");
|
||||
return;
|
||||
return cb();
|
||||
}
|
||||
if(message.data.apool == null)
|
||||
{
|
||||
messageLogger.warn("Dropped message, USER_CHANGES Message has no apool!");
|
||||
return;
|
||||
return cb();
|
||||
}
|
||||
if(message.data.changeset == null)
|
||||
{
|
||||
messageLogger.warn("Dropped message, USER_CHANGES Message has no changeset!");
|
||||
return;
|
||||
return cb();
|
||||
}
|
||||
|
||||
//get all Vars we need
|
||||
|
@ -521,6 +598,9 @@ function handleUserChanges(client, message)
|
|||
var thisSession = sessioninfos[client.id];
|
||||
|
||||
var r, apool, pad;
|
||||
|
||||
// Measure time to process edit
|
||||
var stopWatch = stats.timer('edits').start();
|
||||
|
||||
async.series([
|
||||
//get the pad
|
||||
|
@ -547,23 +627,37 @@ function handleUserChanges(client, message)
|
|||
// defined in the accompanying attribute pool.
|
||||
Changeset.eachAttribNumber(changeset, function(n) {
|
||||
if (! wireApool.getAttrib(n)) {
|
||||
throw "Attribute pool is missing attribute "+n+" for changeset "+changeset;
|
||||
throw new Error("Attribute pool is missing attribute "+n+" for changeset "+changeset);
|
||||
}
|
||||
});
|
||||
|
||||
// Validate all added 'author' attribs to be the same value as the current user
|
||||
var iterator = Changeset.opIterator(Changeset.unpack(changeset).ops)
|
||||
, op
|
||||
while(iterator.hasNext()) {
|
||||
op = iterator.next()
|
||||
if(op.opcode != '+') continue;
|
||||
op.attribs.split('*').forEach(function(attr) {
|
||||
if(!attr) return
|
||||
attr = wireApool.getAttrib(attr)
|
||||
if(!attr) return
|
||||
if('author' == attr[0] && attr[1] != thisSession.author) throw new Error("Trying to submit changes as another author in changeset "+changeset);
|
||||
})
|
||||
}
|
||||
|
||||
//ex. adoptChangesetAttribs
|
||||
|
||||
//Afaik, it copies the new attributes from the changeset, to the global Attribute Pool
|
||||
changeset = Changeset.moveOpsToNewPool(changeset, wireApool, pad.pool);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
// There is an error in this changeset, so just refuse it
|
||||
console.warn("Can't apply USER_CHANGES "+changeset+", because it failed checkRep");
|
||||
client.json.send({disconnect:"badChangeset"});
|
||||
return;
|
||||
stats.meter('failedChangesets').mark();
|
||||
return callback(new Error("Can't apply USER_CHANGES, because "+e.message));
|
||||
}
|
||||
|
||||
//ex. adoptChangesetAttribs
|
||||
|
||||
//Afaik, it copies the new attributes from the changeset, to the global Attribute Pool
|
||||
changeset = Changeset.moveOpsToNewPool(changeset, wireApool, pad.pool);
|
||||
|
||||
//ex. applyUserChanges
|
||||
apool = pad.pool;
|
||||
r = baseRev;
|
||||
|
@ -586,7 +680,14 @@ function handleUserChanges(client, message)
|
|||
// client) are relative to revision r - 1. The follow function
|
||||
// rebases "changeset" so that it is relative to revision r
|
||||
// and can be applied after "c".
|
||||
changeset = Changeset.follow(c, changeset, false, apool);
|
||||
try
|
||||
{
|
||||
changeset = Changeset.follow(c, changeset, false, apool);
|
||||
}catch(e){
|
||||
client.json.send({disconnect:"badChangeset"});
|
||||
stats.meter('failedChangesets').mark();
|
||||
return callback(new Error("Can't apply USER_CHANGES, because "+e.message));
|
||||
}
|
||||
|
||||
if ((r - baseRev) % 200 == 0) { // don't let the stack get too deep
|
||||
async.nextTick(callback);
|
||||
|
@ -606,10 +707,9 @@ function handleUserChanges(client, message)
|
|||
|
||||
if (Changeset.oldLen(changeset) != prevText.length)
|
||||
{
|
||||
console.warn("Can't apply USER_CHANGES "+changeset+" with oldLen " + Changeset.oldLen(changeset) + " to document of length " + prevText.length);
|
||||
client.json.send({disconnect:"badChangeset"});
|
||||
callback();
|
||||
return;
|
||||
stats.meter('failedChangesets').mark();
|
||||
return callback(new Error("Can't apply USER_CHANGES "+changeset+" with oldLen " + Changeset.oldLen(changeset) + " to document of length " + prevText.length));
|
||||
}
|
||||
|
||||
pad.appendRevision(changeset, thisSession.author);
|
||||
|
@ -625,11 +725,16 @@ function handleUserChanges(client, message)
|
|||
pad.appendRevision(nlChangeset);
|
||||
}
|
||||
|
||||
exports.updatePadClients(pad, callback);
|
||||
exports.updatePadClients(pad, function(er) {
|
||||
ERR(er)
|
||||
});
|
||||
callback();
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
ERR(err);
|
||||
stopWatch.end()
|
||||
cb();
|
||||
if(err) console.warn(err.stack || err)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -713,7 +818,7 @@ exports.updatePadClients = function(pad, callback)
|
|||
}
|
||||
|
||||
/**
|
||||
* Copied from the Etherpad Source Code. Don't know what this methode does excatly...
|
||||
* Copied from the Etherpad Source Code. Don't know what this method does excatly...
|
||||
*/
|
||||
function _correctMarkersInPad(atext, apool) {
|
||||
var text = atext.text;
|
||||
|
@ -882,8 +987,7 @@ function handleClientReady(client, message)
|
|||
authorManager.getAuthor(authorId, function(err, author)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
delete author.timestamp;
|
||||
historicalAuthorData[authorId] = author;
|
||||
historicalAuthorData[authorId] = {name: author.name, colorId: author.colorId}; // Filter author attribs (e.g. don't send author's pads to all clients)
|
||||
callback();
|
||||
});
|
||||
}, callback);
|
||||
|
@ -917,6 +1021,11 @@ function handleClientReady(client, message)
|
|||
|
||||
//Log creation/(re-)entering of a pad
|
||||
client.get('remoteAddress', function(er, ip) {
|
||||
//Anonymize the IP address if IP logging is disabled
|
||||
if(settings.disableIPlogging) {
|
||||
ip = 'ANONYMOUS';
|
||||
}
|
||||
|
||||
if(pad.head > 0) {
|
||||
accessLogger.info('[ENTER] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" entered the pad');
|
||||
}
|
||||
|
@ -928,17 +1037,25 @@ function handleClientReady(client, message)
|
|||
//If this is a reconnect, we don't have to send the client the ClientVars again
|
||||
if(message.reconnect == true)
|
||||
{
|
||||
//Join the pad and start receiving updates
|
||||
client.join(padIds.padId);
|
||||
//Save the revision in sessioninfos, we take the revision from the info the client send to us
|
||||
sessioninfos[client.id].rev = message.client_rev;
|
||||
}
|
||||
//This is a normal first connect
|
||||
else
|
||||
{
|
||||
//prepare all values for the wire
|
||||
var atext = Changeset.cloneAText(pad.atext);
|
||||
var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool);
|
||||
var apool = attribsForWire.pool.toJsonable();
|
||||
atext.attribs = attribsForWire.translated;
|
||||
//prepare all values for the wire, there'S a chance that this throws, if the pad is corrupted
|
||||
try {
|
||||
var atext = Changeset.cloneAText(pad.atext);
|
||||
var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool);
|
||||
var apool = attribsForWire.pool.toJsonable();
|
||||
atext.attribs = attribsForWire.translated;
|
||||
}catch(e) {
|
||||
console.error(e.stack || e)
|
||||
client.json.send({disconnect:"corruptPad"});// pull the breaks
|
||||
return callback();
|
||||
}
|
||||
|
||||
// Warning: never ever send padIds.padId to the client. If the
|
||||
// client is read only you would open a security hole 1 swedish
|
||||
|
@ -959,7 +1076,6 @@ function handleClientReady(client, message)
|
|||
"historicalAuthorData": historicalAuthorData,
|
||||
"apool": apool,
|
||||
"rev": pad.getHeadRevisionNumber(),
|
||||
"globalPadId": message.padId,
|
||||
"time": currentTime,
|
||||
},
|
||||
"colorPalette": authorManager.getColorPalette(),
|
||||
|
@ -976,7 +1092,6 @@ function handleClientReady(client, message)
|
|||
"readOnlyId": padIds.readOnlyPadId,
|
||||
"readonly": padIds.readonly,
|
||||
"serverTimestamp": new Date().getTime(),
|
||||
"globalPadId": message.padId,
|
||||
"userId": author,
|
||||
"abiwordAvailable": settings.abiwordAvailable(),
|
||||
"plugins": {
|
||||
|
@ -1035,7 +1150,7 @@ function handleClientReady(client, message)
|
|||
}
|
||||
|
||||
// notify all existing users about new user
|
||||
client.broadcast.to(padIds.padIds).json.send(messageToTheOtherUsers);
|
||||
client.broadcast.to(padIds.padId).json.send(messageToTheOtherUsers);
|
||||
|
||||
//Run trough all sessions of this pad
|
||||
async.forEach(socketio.sockets.clients(padIds.padId), function(roomClient, callback)
|
||||
|
@ -1429,7 +1544,7 @@ function composePadChangesets(padId, startNum, endNum, callback)
|
|||
*/
|
||||
exports.padUsersCount = function (padID, callback) {
|
||||
callback(null, {
|
||||
padUsersCount: socketio.sockets.clients(padId).length
|
||||
padUsersCount: socketio.sockets.clients(padID).length
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1439,7 +1554,7 @@ exports.padUsersCount = function (padID, callback) {
|
|||
exports.padUsers = function (padID, callback) {
|
||||
var result = [];
|
||||
|
||||
async.forEach(socketio.sockets.clients(padId), function(roomClient, callback) {
|
||||
async.forEach(socketio.sockets.clients(padID), function(roomClient, callback) {
|
||||
var s = sessioninfos[roomClient.id];
|
||||
if(s) {
|
||||
authorManager.getAuthor(s.author, function(err, author) {
|
||||
|
@ -1447,6 +1562,7 @@ exports.padUsers = function (padID, callback) {
|
|||
|
||||
author.id = s.author;
|
||||
result.push(author);
|
||||
callback();
|
||||
});
|
||||
}
|
||||
}, function(err) {
|
||||
|
@ -1455,3 +1571,5 @@ exports.padUsers = function (padID, callback) {
|
|||
callback(null, {padUsers: result});
|
||||
});
|
||||
}
|
||||
|
||||
exports.sessioninfos = sessioninfos;
|
||||
|
|
|
@ -23,6 +23,8 @@ var ERR = require("async-stacktrace");
|
|||
var log4js = require('log4js');
|
||||
var messageLogger = log4js.getLogger("message");
|
||||
var securityManager = require("../db/SecurityManager");
|
||||
var readOnlyManager = require("../db/ReadOnlyManager");
|
||||
var settings = require('../utils/Settings');
|
||||
|
||||
/**
|
||||
* Saves all components
|
||||
|
@ -48,88 +50,68 @@ exports.addComponent = function(moduleName, module)
|
|||
/**
|
||||
* sets the socket.io and adds event functions for routing
|
||||
*/
|
||||
exports.setSocketIO = function(_socket)
|
||||
{
|
||||
exports.setSocketIO = function(_socket) {
|
||||
//save this socket internaly
|
||||
socket = _socket;
|
||||
|
||||
socket.sockets.on('connection', function(client)
|
||||
{
|
||||
client.set('remoteAddress', client.handshake.address.address);
|
||||
if(settings.trustProxy && client.handshake.headers['x-forwarded-for'] !== undefined){
|
||||
client.set('remoteAddress', client.handshake.headers['x-forwarded-for']);
|
||||
}
|
||||
else{
|
||||
client.set('remoteAddress', client.handshake.address.address);
|
||||
}
|
||||
var clientAuthorized = false;
|
||||
|
||||
//wrap the original send function to log the messages
|
||||
client._send = client.send;
|
||||
client.send = function(message)
|
||||
{
|
||||
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)
|
||||
{
|
||||
for(var i in components) {
|
||||
components[i].handleConnect(client);
|
||||
}
|
||||
|
||||
//try to handle the message of this client
|
||||
function handleMessage(message)
|
||||
{
|
||||
if(message.component && components[message.component])
|
||||
{
|
||||
//check if component is registered in the components array
|
||||
if(components[message.component])
|
||||
{
|
||||
messageLogger.debug("from " + client.id + ": " + stringifyWithoutPassword(message));
|
||||
components[message.component].handleMessage(client, message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
messageLogger.error("Can't route the message:" + stringifyWithoutPassword(message));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
client.on('message', function(message)
|
||||
{
|
||||
if(message.protocolVersion && message.protocolVersion != 2)
|
||||
{
|
||||
if(message.protocolVersion && message.protocolVersion != 2) {
|
||||
messageLogger.warn("Protocolversion header is not correct:" + stringifyWithoutPassword(message));
|
||||
return;
|
||||
}
|
||||
|
||||
//client is authorized, everything ok
|
||||
if(clientAuthorized)
|
||||
{
|
||||
handleMessage(message);
|
||||
}
|
||||
//try to authorize the client
|
||||
else
|
||||
{
|
||||
//this message has everything to try an authorization
|
||||
if(message.padId !== undefined && message.sessionID !== undefined && message.token !== undefined && message.password !== undefined)
|
||||
{
|
||||
securityManager.checkAccess (message.padId, message.sessionID, message.token, message.password, function(err, statusObject)
|
||||
{
|
||||
if(clientAuthorized) {
|
||||
handleMessage(client, message);
|
||||
} 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") {
|
||||
clientAuthorized = true;
|
||||
handleMessage(message);
|
||||
handleMessage(client, message);
|
||||
}
|
||||
//no access, send the client a message that tell him why
|
||||
else
|
||||
{
|
||||
else {
|
||||
messageLogger.warn("Authentication try failed:" + stringifyWithoutPassword(message));
|
||||
client.json.send({accessStatus: statusObject.accessStatus});
|
||||
}
|
||||
});
|
||||
}
|
||||
//drop message
|
||||
else
|
||||
{
|
||||
};
|
||||
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);
|
||||
});
|
||||
} else {
|
||||
//this message has everything to try an authorization
|
||||
securityManager.checkAccess (message.padId, message.sessionID, message.token, message.password, checkAccessCallback);
|
||||
}
|
||||
} else { //drop message
|
||||
messageLogger.warn("Dropped message cause of bad permissions:" + stringifyWithoutPassword(message));
|
||||
}
|
||||
}
|
||||
|
@ -146,6 +128,21 @@ exports.setSocketIO = function(_socket)
|
|||
});
|
||||
}
|
||||
|
||||
//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]) {
|
||||
messageLogger.debug("from " + client.id + ": " + stringifyWithoutPassword(message));
|
||||
components[message.component].handleMessage(client, message);
|
||||
}
|
||||
} else {
|
||||
messageLogger.error("Can't route the message:" + stringifyWithoutPassword(message));
|
||||
}
|
||||
}
|
||||
|
||||
//returns a stringified representation of a message, removes the password
|
||||
//this ensures there are no passwords in the log
|
||||
function stringifyWithoutPassword(message)
|
||||
|
|
|
@ -19,7 +19,7 @@ exports.createServer = function () {
|
|||
var refPath = rootPath + "/.git/" + ref.substring(5, ref.indexOf("\n"));
|
||||
version = fs.readFileSync(refPath, "utf-8");
|
||||
version = version.substring(0, 7);
|
||||
console.log("Your Etherpad Lite git version is " + version);
|
||||
console.log("Your Etherpad git version is " + version);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
|
@ -27,11 +27,11 @@ exports.createServer = function () {
|
|||
}
|
||||
console.log("Report bugs at https://github.com/ether/etherpad-lite/issues")
|
||||
|
||||
serverName = "Etherpad-Lite " + version + " (http://etherpad.org)";
|
||||
serverName = "Etherpad " + version + " (http://etherpad.org)";
|
||||
|
||||
exports.restartServer();
|
||||
|
||||
console.log("You can access your Etherpad-Lite instance at http://" + settings.ip + ":" + settings.port + "/");
|
||||
console.log("You can access your Etherpad instance at http://" + settings.ip + ":" + settings.port + "/");
|
||||
if(!_.isEmpty(settings.users)){
|
||||
console.log("The plugin admin page is at http://" + settings.ip + ":" + settings.port + "/admin/plugins");
|
||||
}
|
||||
|
@ -75,6 +75,10 @@ exports.restartServer = function () {
|
|||
next();
|
||||
});
|
||||
|
||||
if(settings.trustProxy){
|
||||
app.enable('trust proxy');
|
||||
}
|
||||
|
||||
app.configure(function() {
|
||||
hooks.callAll("expressConfigure", {"app": app});
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@ var eejs = require('ep_etherpad-lite/node/eejs');
|
|||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
args.app.get('/admin', function(req, res) {
|
||||
if('/' != req.path[req.path.length-1]) return res.redirect('/admin/');
|
||||
res.send( eejs.require("ep_etherpad-lite/templates/admin/index.html", {}) );
|
||||
});
|
||||
}
|
||||
|
|
|
@ -27,49 +27,84 @@ exports.socketio = function (hook_name, args, cb) {
|
|||
io.on('connection', function (socket) {
|
||||
if (!socket.handshake.session.user || !socket.handshake.session.user.is_admin) return;
|
||||
|
||||
socket.on("load", function (query) {
|
||||
socket.on("getInstalled", function (query) {
|
||||
// send currently installed plugins
|
||||
socket.emit("installed-results", {results: plugins.plugins});
|
||||
socket.emit("progress", {progress:1});
|
||||
var installed = Object.keys(plugins.plugins).map(function(plugin) {
|
||||
return plugins.plugins[plugin].package
|
||||
})
|
||||
socket.emit("results:installed", {installed: installed});
|
||||
});
|
||||
|
||||
socket.on("checkUpdates", function() {
|
||||
socket.emit("progress", {progress:0, message:'Checking for plugin updates...'});
|
||||
// Check plugins for updates
|
||||
installer.search({offset: 0, pattern: '', limit: 500}, /*useCache:*/true, function(data) { // hacky
|
||||
if (!data.results) return;
|
||||
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(!data.results[plugin]) return false;
|
||||
var latestVersion = data.results[plugin]['dist-tags'].latest
|
||||
if(!results[plugin]) return false;
|
||||
var latestVersion = results[plugin].version
|
||||
var currentVersion = plugins.plugins[plugin].package.version
|
||||
return semver.gt(latestVersion, currentVersion)
|
||||
});
|
||||
socket.emit("updatable", {updatable: updatable});
|
||||
socket.emit("progress", {progress:1});
|
||||
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.emit("results:available", results);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("search", function (query) {
|
||||
socket.emit("progress", {progress:0, message:'Fetching results...'});
|
||||
installer.search(query, true, function (progress) {
|
||||
if (progress.results)
|
||||
socket.emit("search-result", progress);
|
||||
socket.emit("progress", progress);
|
||||
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]
|
||||
})
|
||||
.filter(function(plugin) {
|
||||
return !plugins.plugins[plugin.name]
|
||||
});
|
||||
res = sortPluginList(res, query.sortBy, query.sortDir)
|
||||
.slice(query.offset, query.offset+query.limit);
|
||||
socket.emit("results:search", {results: res, query: query});
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("install", function (plugin_name) {
|
||||
socket.emit("progress", {progress:0, message:'Downloading and installing ' + plugin_name + "..."});
|
||||
installer.install(plugin_name, function (progress) {
|
||||
socket.emit("progress", progress);
|
||||
installer.install(plugin_name, function (er) {
|
||||
if(er) console.warn(er)
|
||||
socket.emit("finished:install", {plugin: plugin_name, error: er? er.message : null});
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("uninstall", function (plugin_name) {
|
||||
socket.emit("progress", {progress:0, message:'Uninstalling ' + plugin_name + "..."});
|
||||
installer.uninstall(plugin_name, function (progress) {
|
||||
socket.emit("progress", progress);
|
||||
installer.uninstall(plugin_name, function (er) {
|
||||
if(er) console.warn(er)
|
||||
socket.emit("finished:uninstall", {plugin: plugin_name, error: er? er.message : null});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sortPluginList(plugins, property, /*ASC?*/dir) {
|
||||
return plugins.sort(function(a, b) {
|
||||
if (a[property] < b[property])
|
||||
return dir? -1 : 1;
|
||||
if (a[property] > b[property])
|
||||
return dir? 1 : -1;
|
||||
// a must be equal to b
|
||||
return 0;
|
||||
})
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
var log4js = require('log4js');
|
||||
var apiLogger = log4js.getLogger("API");
|
||||
var clientLogger = log4js.getLogger("client");
|
||||
var formidable = require('formidable');
|
||||
var apiHandler = require('../../handler/APIHandler');
|
||||
|
||||
|
@ -42,10 +43,10 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
});
|
||||
});
|
||||
|
||||
//The Etherpad client side sends information about how a disconnect happen
|
||||
//The Etherpad client side sends information about how a disconnect happened
|
||||
args.app.post('/ep/pad/connection-diagnostic-info', function(req, res) {
|
||||
new formidable.IncomingForm().parse(req, function(err, fields, files) {
|
||||
console.log("DIAGNOSTIC-INFO: " + fields.diagnosticInfo);
|
||||
clientLogger.info("DIAGNOSTIC-INFO: " + fields.diagnosticInfo);
|
||||
res.end("OK");
|
||||
});
|
||||
});
|
||||
|
@ -53,8 +54,18 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
//The Etherpad client side sends information about client side javscript errors
|
||||
args.app.post('/jserror', function(req, res) {
|
||||
new formidable.IncomingForm().parse(req, function(err, fields, files) {
|
||||
console.error("CLIENT SIDE JAVASCRIPT ERROR: " + fields.errorInfo);
|
||||
try {
|
||||
var data = JSON.parse(fields.errorInfo)
|
||||
}catch(e){
|
||||
return res.end()
|
||||
}
|
||||
clientLogger.warn(data.msg+' --', data);
|
||||
res.end("OK");
|
||||
});
|
||||
});
|
||||
|
||||
//Provide a possibility to query the latest available API version
|
||||
args.app.get('/api', function (req, res) {
|
||||
res.json({"currentVersion" : apiHandler.latestApiVersion});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
var os = require("os");
|
||||
var db = require('../../db/DB');
|
||||
var stats = require('ep_etherpad-lite/node/stats')
|
||||
|
||||
|
||||
exports.onShutdown = false;
|
||||
|
@ -28,6 +29,7 @@ exports.gracefulShutdown = function(err) {
|
|||
}, 3000);
|
||||
}
|
||||
|
||||
process.on('uncaughtException', exports.gracefulShutdown);
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
exports.app = args.app;
|
||||
|
@ -39,6 +41,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
// allowing you to respond however you like
|
||||
res.send(500, { error: 'Sorry, something bad happened!' });
|
||||
console.error(err.stack? err.stack : err.toString());
|
||||
stats.meter('http500').mark()
|
||||
})
|
||||
|
||||
//connect graceful shutdown with sigint and uncaughtexception
|
||||
|
@ -47,6 +50,4 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
//https://github.com/joyent/node/issues/1553
|
||||
process.on('SIGINT', exports.gracefulShutdown);
|
||||
}
|
||||
|
||||
process.on('uncaughtException', exports.gracefulShutdown);
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
//if abiword is disabled, and this is a format we only support with abiword, output a message
|
||||
if (settings.abiword == null &&
|
||||
["odt", "pdf", "doc"].indexOf(req.params.type) !== -1) {
|
||||
res.send("Abiword is not enabled at this Etherpad Lite instance. Set the path to Abiword in settings.json to enable this feature");
|
||||
res.send("Abiword is not enabled at this Etherpad instance. Set the path to Abiword in settings.json to enable this feature");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,50 +16,50 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
//translate the read only pad to a padId
|
||||
function(callback)
|
||||
{
|
||||
readOnlyManager.getPadId(req.params.id, function(err, _padId)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
readOnlyManager.getPadId(req.params.id, function(err, _padId)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
padId = _padId;
|
||||
padId = _padId;
|
||||
|
||||
//we need that to tell hasPadAcess about the pad
|
||||
req.params.pad = padId;
|
||||
//we need that to tell hasPadAcess about the pad
|
||||
req.params.pad = padId;
|
||||
|
||||
callback();
|
||||
});
|
||||
callback();
|
||||
});
|
||||
},
|
||||
//render the html document
|
||||
function(callback)
|
||||
{
|
||||
//return if the there is no padId
|
||||
if(padId == null)
|
||||
{
|
||||
callback("notfound");
|
||||
return;
|
||||
}
|
||||
//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, false, function(err, _html)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
html = _html;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
hasPadAccess(req, res, function()
|
||||
{
|
||||
//render the html document
|
||||
exporthtml.getPadHTMLDocument(padId, null, false, function(err, _html)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
html = _html;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
//throw any unexpected error
|
||||
if(err && err != "notfound")
|
||||
ERR(err);
|
||||
ERR(err);
|
||||
|
||||
if(err == "notfound")
|
||||
res.send(404, '404 - Not Found');
|
||||
res.send(404, '404 - Not Found');
|
||||
else
|
||||
res.send(html);
|
||||
res.send(html);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,20 +12,20 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
else
|
||||
{
|
||||
padManager.sanitizePadId(padId, function(sanitizedPadId) {
|
||||
//the pad id was sanitized, so we redirect to the sanitized version
|
||||
if(sanitizedPadId != padId)
|
||||
{
|
||||
//the pad id was sanitized, so we redirect to the sanitized version
|
||||
if(sanitizedPadId != padId)
|
||||
{
|
||||
var real_url = sanitizedPadId;
|
||||
var query = url.parse(req.url).query;
|
||||
if ( query ) real_url += '?' + query;
|
||||
res.header('Location', real_url);
|
||||
res.send(302, 'You should be redirected to <a href="' + real_url + '">' + real_url + '</a>');
|
||||
}
|
||||
//the pad id was fine, so just render it
|
||||
else
|
||||
{
|
||||
next();
|
||||
}
|
||||
res.header('Location', real_url);
|
||||
res.send(302, 'You should be redirected to <a href="' + real_url + '">' + real_url + '</a>');
|
||||
}
|
||||
//the pad id was fine, so just render it
|
||||
else
|
||||
{
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -2,6 +2,10 @@ var path = require('path');
|
|||
var eejs = require('ep_etherpad-lite/node/eejs');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
// expose current stats
|
||||
args.app.get('/stats', function(req, res) {
|
||||
res.json(require('ep_etherpad-lite/node/stats').toJSON())
|
||||
})
|
||||
|
||||
//serve index.html under /
|
||||
args.app.get('/', function(req, res)
|
||||
|
@ -45,11 +49,11 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
//there is no custom favicon, send the default favicon
|
||||
if(err)
|
||||
{
|
||||
filePath = path.normalize(__dirname + "/../../../static/favicon.ico");
|
||||
res.sendfile(filePath);
|
||||
filePath = path.normalize(__dirname + "/../../../static/favicon.ico");
|
||||
res.sendfile(filePath);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
430
src/node/hooks/express/swagger.js
Normal file
430
src/node/hooks/express/swagger.js
Normal file
|
@ -0,0 +1,430 @@
|
|||
var log4js = require('log4js');
|
||||
var express = require('express');
|
||||
var apiHandler = require('../../handler/APIHandler');
|
||||
var apiCaller = require('./apicalls').apiCaller;
|
||||
var settings = require("../../utils/Settings");
|
||||
|
||||
var swaggerModels = {
|
||||
'models': {
|
||||
'SessionInfo' : {
|
||||
"id": 'SessionInfo',
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"authorID": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupID":{
|
||||
"type":"string"
|
||||
},
|
||||
"validUntil":{
|
||||
"type":"long"
|
||||
}
|
||||
}
|
||||
},
|
||||
'UserInfo' : {
|
||||
"id": 'UserInfo',
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"colorId": {
|
||||
"type": "string"
|
||||
},
|
||||
"name":{
|
||||
"type":"string"
|
||||
},
|
||||
"timestamp":{
|
||||
"type":"long"
|
||||
}
|
||||
}
|
||||
},
|
||||
'Message' : {
|
||||
"id": 'Message',
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"userId": {
|
||||
"type": "string"
|
||||
},
|
||||
"userName":{
|
||||
"type":"string"
|
||||
},
|
||||
"time":{
|
||||
"type":"long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function sessionListResponseProcessor(res) {
|
||||
if (res.data) {
|
||||
var sessions = [];
|
||||
for (var sessionId in res.data) {
|
||||
var sessionInfo = res.data[sessionId];
|
||||
sessionId["id"] = sessionId;
|
||||
sessions.push(sessionInfo);
|
||||
}
|
||||
res.data = sessions;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// We'll add some more info to the API methods
|
||||
var API = {
|
||||
|
||||
// Group
|
||||
"group": {
|
||||
"create" : {
|
||||
"func" : "createGroup",
|
||||
"description": "creates a new group",
|
||||
"response": {"groupID":{"type":"string"}}
|
||||
},
|
||||
"createIfNotExistsFor" : {
|
||||
"func": "createGroupIfNotExistsFor",
|
||||
"description": "this functions helps you to map your application group ids to Etherpad group ids",
|
||||
"response": {"groupID":{"type":"string"}}
|
||||
},
|
||||
"delete" : {
|
||||
"func": "deleteGroup",
|
||||
"description": "deletes a group"
|
||||
},
|
||||
"listPads" : {
|
||||
"func": "listPads",
|
||||
"description": "returns all pads of this group",
|
||||
"response": {"padIDs":{"type":"List", "items":{"type":"string"}}}
|
||||
},
|
||||
"createPad" : {
|
||||
"func": "createGroupPad",
|
||||
"description": "creates a new pad in this group"
|
||||
},
|
||||
"listSessions": {
|
||||
"func": "listSessionsOfGroup",
|
||||
"responseProcessor": sessionListResponseProcessor,
|
||||
"description": "",
|
||||
"response": {"sessions":{"type":"List", "items":{"type":"SessionInfo"}}}
|
||||
},
|
||||
"list": {
|
||||
"func": "listAllGroups",
|
||||
"description": "",
|
||||
"response": {"groupIDs":{"type":"List", "items":{"type":"string"}}}
|
||||
},
|
||||
},
|
||||
|
||||
// Author
|
||||
"author": {
|
||||
"create" : {
|
||||
"func" : "createAuthor",
|
||||
"description": "creates a new author",
|
||||
"response": {"authorID":{"type":"string"}}
|
||||
},
|
||||
"createIfNotExistsFor": {
|
||||
"func": "createAuthorIfNotExistsFor",
|
||||
"description": "this functions helps you to map your application author ids to Etherpad author ids",
|
||||
"response": {"authorID":{"type":"string"}}
|
||||
},
|
||||
"listPads": {
|
||||
"func": "listPadsOfAuthor",
|
||||
"description": "returns an array of all pads this author contributed to",
|
||||
"response": {"padIDs":{"type":"List", "items":{"type":"string"}}}
|
||||
},
|
||||
"listSessions": {
|
||||
"func": "listSessionsOfAuthor",
|
||||
"responseProcessor": sessionListResponseProcessor,
|
||||
"description": "returns all sessions of an author",
|
||||
"response": {"sessions":{"type":"List", "items":{"type":"SessionInfo"}}}
|
||||
},
|
||||
// We need an operation that return a UserInfo so it can be picked up by the codegen :(
|
||||
"getName" : {
|
||||
"func": "getAuthorName",
|
||||
"description": "Returns the Author Name of the author",
|
||||
"responseProcessor": function(response) {
|
||||
if (response.data) {
|
||||
response["info"] = {"name": response.data.authorName};
|
||||
delete response["data"];
|
||||
}
|
||||
},
|
||||
"response": {"info":{"type":"UserInfo"}}
|
||||
}
|
||||
},
|
||||
"session": {
|
||||
"create" : {
|
||||
"func": "createSession",
|
||||
"description": "creates a new session. validUntil is an unix timestamp in seconds",
|
||||
"response": {"sessionID":{"type":"string"}}
|
||||
},
|
||||
"delete" : {
|
||||
"func": "deleteSession",
|
||||
"description": "deletes a session"
|
||||
},
|
||||
// We need an operation that returns a SessionInfo so it can be picked up by the codegen :(
|
||||
"info": {
|
||||
"func": "getSessionInfo",
|
||||
"description": "returns informations about a session",
|
||||
"responseProcessor": function(response) {
|
||||
// move this to info
|
||||
if (response.data) {
|
||||
response["info"] = response.data;
|
||||
delete response["data"];
|
||||
}
|
||||
},
|
||||
"response": {"info":{"type":"SessionInfo"}}
|
||||
}
|
||||
},
|
||||
"pad": {
|
||||
"listAll" : {
|
||||
"func": "listAllPads",
|
||||
"description": "list all the pads",
|
||||
"response": {"padIDs":{"type":"List", "items": {"type" : "string"}}}
|
||||
},
|
||||
"createDiffHTML" : {
|
||||
"func" : "createDiffHTML",
|
||||
"description": "",
|
||||
"response": {}
|
||||
},
|
||||
"create" : {
|
||||
"func" : "createPad",
|
||||
"description": "creates a new (non-group) pad. Note that if you need to create a group Pad, you should call createGroupPad"
|
||||
},
|
||||
"getText" : {
|
||||
"func" : "getText",
|
||||
"description": "returns the text of a pad",
|
||||
"response": {"text":{"type":"string"}}
|
||||
},
|
||||
"setText" : {
|
||||
"func" : "setText",
|
||||
"description": "sets the text of a pad"
|
||||
},
|
||||
"getHTML": {
|
||||
"func" : "getHTML",
|
||||
"description": "returns the text of a pad formatted as HTML",
|
||||
"response": {"html":{"type":"string"}}
|
||||
},
|
||||
"setHTML": {
|
||||
"func" : "setHTML",
|
||||
"description": "sets the text of a pad with HTML"
|
||||
},
|
||||
"getRevisionsCount": {
|
||||
"func" : "getRevisionsCount",
|
||||
"description": "returns the number of revisions of this pad",
|
||||
"response": {"revisions":{"type":"long"}}
|
||||
},
|
||||
"getLastEdited": {
|
||||
"func" : "getLastEdited",
|
||||
"description": "returns the timestamp of the last revision of the pad",
|
||||
"response": {"lastEdited":{"type":"long"}}
|
||||
},
|
||||
"delete": {
|
||||
"func" : "deletePad",
|
||||
"description": "deletes a pad"
|
||||
},
|
||||
"getReadOnlyID": {
|
||||
"func" : "getReadOnlyID",
|
||||
"description": "returns the read only link of a pad",
|
||||
"response": {"readOnlyID":{"type":"string"}}
|
||||
},
|
||||
"setPublicStatus": {
|
||||
"func": "setPublicStatus",
|
||||
"description": "sets a boolean for the public status of a pad"
|
||||
},
|
||||
"getPublicStatus": {
|
||||
"func": "getPublicStatus",
|
||||
"description": "return true of false",
|
||||
"response": {"publicStatus":{"type":"boolean"}}
|
||||
},
|
||||
"setPassword": {
|
||||
"func": "setPassword",
|
||||
"description": "returns ok or a error message"
|
||||
},
|
||||
"isPasswordProtected": {
|
||||
"func": "isPasswordProtected",
|
||||
"description": "returns true or false",
|
||||
"response": {"passwordProtection":{"type":"boolean"}}
|
||||
},
|
||||
"authors": {
|
||||
"func": "listAuthorsOfPad",
|
||||
"description": "returns an array of authors who contributed to this pad",
|
||||
"response": {"authorIDs":{"type":"List", "items":{"type" : "string"}}}
|
||||
},
|
||||
"usersCount": {
|
||||
"func": "padUsersCount",
|
||||
"description": "returns the number of user that are currently editing this pad",
|
||||
"response": {"padUsersCount":{"type": "long"}}
|
||||
},
|
||||
"users": {
|
||||
"func": "padUsers",
|
||||
"description": "returns the list of users that are currently editing this pad",
|
||||
"response": {"padUsers":{"type":"List", "items":{"type": "UserInfo"}}}
|
||||
},
|
||||
"sendClientsMessage": {
|
||||
"func": "sendClientsMessage",
|
||||
"description": "sends a custom message of type msg to the pad"
|
||||
},
|
||||
"checkToken" : {
|
||||
"func": "checkToken",
|
||||
"description": "returns ok when the current api token is valid"
|
||||
},
|
||||
"getChatHistory": {
|
||||
"func": "getChatHistory",
|
||||
"description": "returns the chat history",
|
||||
"response": {"messages":{"type":"List", "items": {"type" : "Message"}}}
|
||||
},
|
||||
// We need an operation that returns a Message so it can be picked up by the codegen :(
|
||||
"getChatHead": {
|
||||
"func": "getChatHead",
|
||||
"description": "returns the chatHead (chat-message) of the pad",
|
||||
"responseProcessor": function(response) {
|
||||
// move this to info
|
||||
if (response.data) {
|
||||
response["chatHead"] = {"time": response.data["chatHead"]};
|
||||
delete response["data"];
|
||||
}
|
||||
},
|
||||
"response": {"chatHead":{"type":"Message"}}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function capitalise(string){
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
for (var resource in API) {
|
||||
for (var func in API[resource]) {
|
||||
|
||||
// The base response model
|
||||
var responseModel = {
|
||||
"properties": {
|
||||
"code":{
|
||||
"type":"int"
|
||||
},
|
||||
"message":{
|
||||
"type":"string"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var responseModelId = "Response";
|
||||
|
||||
// Add the data properties (if any) to the response
|
||||
if (API[resource][func]["response"]) {
|
||||
// This is a specific response so let's set a new id
|
||||
responseModelId = capitalise(resource) + capitalise(func) + "Response";
|
||||
|
||||
for(var prop in API[resource][func]["response"]) {
|
||||
var propType = API[resource][func]["response"][prop];
|
||||
responseModel["properties"][prop] = propType;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the id
|
||||
responseModel["id"] = responseModelId;
|
||||
|
||||
// Add this to the swagger models
|
||||
swaggerModels['models'][responseModelId] = responseModel;
|
||||
|
||||
// Store the response model id
|
||||
API[resource][func]["responseClass"] = responseModelId;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function newSwagger() {
|
||||
var swagger_module = require.resolve("swagger-node-express");
|
||||
if (require.cache[swagger_module]) {
|
||||
// delete the child modules from cache
|
||||
require.cache[swagger_module].children.forEach(function(m) {delete require.cache[m.id];});
|
||||
// delete the module from cache
|
||||
delete require.cache[swagger_module];
|
||||
}
|
||||
return require("swagger-node-express");
|
||||
}
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
|
||||
for (var version in apiHandler.version) {
|
||||
|
||||
var swagger = newSwagger();
|
||||
var basePath = "/rest/" + version;
|
||||
|
||||
// Let's put this under /rest for now
|
||||
var subpath = express();
|
||||
|
||||
args.app.use(basePath, subpath);
|
||||
|
||||
swagger.setAppHandler(subpath);
|
||||
|
||||
swagger.addModels(swaggerModels);
|
||||
|
||||
for (var resource in API) {
|
||||
|
||||
for (var funcName in API[resource]) {
|
||||
var func = API[resource][funcName];
|
||||
|
||||
// get the api function
|
||||
var apiFunc = apiHandler.version[version][func["func"]];
|
||||
|
||||
// Skip this one if it does not exist in the version
|
||||
if(!apiFunc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var swaggerFunc = {
|
||||
'spec': {
|
||||
"description" : func["description"],
|
||||
"path" : "/" + resource + "/" + funcName,
|
||||
"summary" : funcName,
|
||||
"nickname" : funcName,
|
||||
"method": "GET",
|
||||
"params" : apiFunc.map( function(param) {
|
||||
return swagger.queryParam(param, param, "string");
|
||||
}),
|
||||
"responseClass" : func["responseClass"]
|
||||
},
|
||||
'action': (function(func, responseProcessor) {
|
||||
return function (req,res) {
|
||||
req.params.version = version;
|
||||
req.params.func = func; // call the api function
|
||||
|
||||
//wrap the send function so we can process the response
|
||||
res.__swagger_send = res.send;
|
||||
res.send = function (response) {
|
||||
// ugly but we need to get this as json
|
||||
response = JSON.parse(response);
|
||||
// process the response if needed
|
||||
if (responseProcessor) {
|
||||
response = responseProcessor(response);
|
||||
}
|
||||
// Let's move everything out of "data"
|
||||
if (response.data) {
|
||||
for(var prop in response.data) {
|
||||
response[prop] = response.data[prop];
|
||||
delete response.data;
|
||||
}
|
||||
}
|
||||
response = JSON.stringify(response);
|
||||
res.__swagger_send(response);
|
||||
};
|
||||
|
||||
apiCaller(req, res, req.query);
|
||||
};
|
||||
})(func["func"], func["responseProcessor"]) // must use a closure here
|
||||
};
|
||||
|
||||
swagger.addGet(swaggerFunc);
|
||||
}
|
||||
}
|
||||
|
||||
swagger.setHeaders = function setHeaders(res) {
|
||||
res.header('Access-Control-Allow-Origin', "*");
|
||||
};
|
||||
|
||||
swagger.configureSwaggerPaths("", "/api" , "");
|
||||
|
||||
swagger.configure("http://" + settings.ip + ":" + settings.port + basePath, version);
|
||||
}
|
||||
};
|
|
@ -4,7 +4,8 @@ var httpLogger = log4js.getLogger("http");
|
|||
var settings = require('../../utils/Settings');
|
||||
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
||||
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
|
||||
var ueberStore = require('../../db/SessionStore');
|
||||
var stats = require('ep_etherpad-lite/node/stats')
|
||||
|
||||
//checks for basic http auth
|
||||
exports.basicAuth = function (req, res, next) {
|
||||
|
@ -32,8 +33,8 @@ exports.basicAuth = function (req, res, next) {
|
|||
// If auth headers are present use them to authenticate...
|
||||
if (req.headers.authorization && req.headers.authorization.search('Basic ') === 0) {
|
||||
var userpass = new Buffer(req.headers.authorization.split(' ')[1], 'base64').toString().split(":")
|
||||
var username = userpass[0];
|
||||
var password = userpass[1];
|
||||
var username = userpass.shift();
|
||||
var password = userpass.join(':');
|
||||
|
||||
if (settings.users[username] != undefined && settings.users[username].password == password) {
|
||||
settings.users[username].username = username;
|
||||
|
@ -91,10 +92,21 @@ exports.basicAuth = function (req, res, next) {
|
|||
exports.secret = null;
|
||||
|
||||
exports.expressConfigure = function (hook_name, args, cb) {
|
||||
// Measure response time
|
||||
args.app.use(function(req, res, next) {
|
||||
var stopWatch = stats.timer('httpRequests').start();
|
||||
var sendFn = res.send
|
||||
res.send = function() {
|
||||
stopWatch.end()
|
||||
sendFn.apply(res, arguments)
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
// If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158.
|
||||
// Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway.
|
||||
if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR"))
|
||||
args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'}));
|
||||
args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.DEBUG, format: ':status, :method :url'}));
|
||||
|
||||
/* Do not let express create the session, so that we can retain a
|
||||
* reference to it for socket.io to use. Also, set the key (cookie
|
||||
|
@ -102,15 +114,14 @@ exports.expressConfigure = function (hook_name, args, cb) {
|
|||
* handling it cleaner :) */
|
||||
|
||||
if (!exports.sessionStore) {
|
||||
exports.sessionStore = new express.session.MemoryStore();
|
||||
exports.secret = randomString(32);
|
||||
exports.sessionStore = new ueberStore();
|
||||
exports.secret = settings.sessionKey; // Isn't this being reset each time the server spawns?
|
||||
}
|
||||
|
||||
args.app.use(express.cookieParser(exports.secret));
|
||||
|
||||
args.app.use(express.cookieParser(exports.secret));
|
||||
args.app.sessionStore = exports.sessionStore;
|
||||
args.app.use(express.session({store: args.app.sessionStore,
|
||||
key: 'express_sid' }));
|
||||
args.app.use(express.session({secret: exports.secret, store: args.app.sessionStore, key: 'express_sid' }));
|
||||
|
||||
args.app.use(exports.basicAuth);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,10 +23,15 @@
|
|||
|
||||
var log4js = require('log4js')
|
||||
, async = require('async')
|
||||
, stats = require('./stats')
|
||||
;
|
||||
|
||||
log4js.replaceConsole();
|
||||
|
||||
stats.gauge('memoryUsage', function() {
|
||||
return process.memoryUsage().rss
|
||||
})
|
||||
|
||||
var settings
|
||||
, db
|
||||
, plugins
|
||||
|
@ -48,7 +53,6 @@ async.waterfall([
|
|||
plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
|
||||
hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
|
||||
hooks.plugins = plugins;
|
||||
|
||||
callback();
|
||||
},
|
||||
|
||||
|
|
3
src/node/stats.js
Normal file
3
src/node/stats.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
var measured = require('measured')
|
||||
|
||||
module.exports = measured.createCollection();
|
|
@ -63,7 +63,7 @@ if(os.type().indexOf("Windows") > -1)
|
|||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.convertFile = function(srcFile, destFile, type, callback)
|
||||
{
|
||||
|
@ -100,7 +100,7 @@ else
|
|||
{
|
||||
//add data to buffer
|
||||
stdoutBuffer+=data.toString();
|
||||
|
||||
|
||||
//we're searching for the prompt, cause this means everything we need is in the buffer
|
||||
if(stdoutBuffer.search("AbiWord:>") != -1)
|
||||
{
|
||||
|
@ -121,27 +121,29 @@ else
|
|||
firstPrompt = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
spawnAbiword();
|
||||
|
||||
doConvertTask = function(task, callback)
|
||||
{
|
||||
abiword.stdin.write("convert " + task.srcFile + " " + task.destFile + " " + task.type + "\n");
|
||||
|
||||
//create a callback that calls the task callback and the caller callback
|
||||
stdoutCallback = function (err)
|
||||
{
|
||||
callback();
|
||||
console.log("queue continue");
|
||||
task.callback(err);
|
||||
try{
|
||||
task.callback(err);
|
||||
}catch(e){
|
||||
console.error("Abiword File failed to convert", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
//Queue with the converts we have to do
|
||||
var queue = async.queue(doConvertTask, 1);
|
||||
|
||||
exports.convertFile = function(srcFile, destFile, type, callback)
|
||||
{
|
||||
{
|
||||
queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -316,7 +316,7 @@ exports.getPadDokuWikiDocument = function (padId, revNum, callback)
|
|||
|
||||
getPadDokuWiki(pad, revNum, callback);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function _escapeDokuWiki(s)
|
||||
{
|
||||
|
|
87
src/node/utils/ExportHelper.js
Normal file
87
src/node/utils/ExportHelper.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* Helpers for export requests
|
||||
*/
|
||||
|
||||
/*
|
||||
* 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var async = require("async");
|
||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||
var padManager = require("../db/PadManager");
|
||||
var ERR = require("async-stacktrace");
|
||||
var Security = require('ep_etherpad-lite/static/js/security');
|
||||
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
|
||||
exports.getPadPlainText = function(pad, revNum){
|
||||
var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext());
|
||||
var textLines = atext.text.slice(0, -1).split('\n');
|
||||
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||
var apool = pad.pool();
|
||||
|
||||
var pieces = [];
|
||||
for (var i = 0; i < textLines.length; i++){
|
||||
var line = _analyzeLine(textLines[i], attribLines[i], apool);
|
||||
if (line.listLevel){
|
||||
var numSpaces = line.listLevel * 2 - 1;
|
||||
var bullet = '*';
|
||||
pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n');
|
||||
}
|
||||
else{
|
||||
pieces.push(line.text, '\n');
|
||||
}
|
||||
}
|
||||
|
||||
return pieces.join('');
|
||||
};
|
||||
|
||||
|
||||
exports._analyzeLine = function(text, aline, apool){
|
||||
var line = {};
|
||||
|
||||
// identify list
|
||||
var lineMarker = 0;
|
||||
line.listLevel = 0;
|
||||
if (aline){
|
||||
var opIter = Changeset.opIterator(aline);
|
||||
if (opIter.hasNext()){
|
||||
var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool);
|
||||
if (listType){
|
||||
lineMarker = 1;
|
||||
listType = /([a-z]+)([12345678])/.exec(listType);
|
||||
if (listType){
|
||||
line.listTypeName = listType[1];
|
||||
line.listLevel = Number(listType[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lineMarker){
|
||||
line.text = text.substring(1);
|
||||
line.aline = Changeset.subattribution(aline, 1);
|
||||
}
|
||||
else{
|
||||
line.text = text;
|
||||
line.aline = aline;
|
||||
}
|
||||
return line;
|
||||
};
|
||||
|
||||
|
||||
exports._encodeWhitespace = function(s){
|
||||
return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c){
|
||||
return "&#" +c.charCodeAt(0) + ";";
|
||||
});
|
||||
};
|
|
@ -21,31 +21,9 @@ var padManager = require("../db/PadManager");
|
|||
var ERR = require("async-stacktrace");
|
||||
var Security = require('ep_etherpad-lite/static/js/security');
|
||||
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
function getPadPlainText(pad, revNum)
|
||||
{
|
||||
var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext());
|
||||
var textLines = atext.text.slice(0, -1).split('\n');
|
||||
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||
var apool = pad.pool();
|
||||
|
||||
var pieces = [];
|
||||
for (var i = 0; i < textLines.length; i++)
|
||||
{
|
||||
var line = _analyzeLine(textLines[i], attribLines[i], apool);
|
||||
if (line.listLevel)
|
||||
{
|
||||
var numSpaces = line.listLevel * 2 - 1;
|
||||
var bullet = '*';
|
||||
pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n');
|
||||
}
|
||||
else
|
||||
{
|
||||
pieces.push(line.text, '\n');
|
||||
}
|
||||
}
|
||||
|
||||
return pieces.join('');
|
||||
}
|
||||
var getPadPlainText = require('./ExportHelper').getPadPlainText;
|
||||
var _analyzeLine = require('./ExportHelper')._analyzeLine;
|
||||
var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
|
||||
|
||||
function getPadHTML(pad, revNum, callback)
|
||||
{
|
||||
|
@ -469,7 +447,7 @@ function getHTMLFromAtext(pad, atext, authorColors)
|
|||
pieces.push('</li></ul>');
|
||||
}
|
||||
lists.length--;
|
||||
}
|
||||
}
|
||||
var lineContentFromHook = hooks.callAllStr("getLineHTMLForExport",
|
||||
{
|
||||
line: line,
|
||||
|
@ -477,14 +455,14 @@ function getHTMLFromAtext(pad, atext, authorColors)
|
|||
attribLine: attribLines[i],
|
||||
text: textLines[i]
|
||||
}, " ", " ", "");
|
||||
if (lineContentFromHook)
|
||||
{
|
||||
pieces.push(lineContentFromHook, '');
|
||||
}
|
||||
else
|
||||
{
|
||||
pieces.push(lineContent, '<br>');
|
||||
}
|
||||
if (lineContentFromHook)
|
||||
{
|
||||
pieces.push(lineContentFromHook, '');
|
||||
}
|
||||
else
|
||||
{
|
||||
pieces.push(lineContent, '<br>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -503,45 +481,6 @@ function getHTMLFromAtext(pad, atext, authorColors)
|
|||
return pieces.join('');
|
||||
}
|
||||
|
||||
function _analyzeLine(text, aline, apool)
|
||||
{
|
||||
var line = {};
|
||||
|
||||
// identify list
|
||||
var lineMarker = 0;
|
||||
line.listLevel = 0;
|
||||
if (aline)
|
||||
{
|
||||
var opIter = Changeset.opIterator(aline);
|
||||
if (opIter.hasNext())
|
||||
{
|
||||
var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool);
|
||||
if (listType)
|
||||
{
|
||||
lineMarker = 1;
|
||||
listType = /([a-z]+)([12345678])/.exec(listType);
|
||||
if (listType)
|
||||
{
|
||||
line.listTypeName = listType[1];
|
||||
line.listLevel = Number(listType[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lineMarker)
|
||||
{
|
||||
line.text = text.substring(1);
|
||||
line.aline = Changeset.subattribution(aline, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
line.text = text;
|
||||
line.aline = aline;
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
|
||||
{
|
||||
padManager.getPad(padId, function (err, pad)
|
||||
|
@ -551,7 +490,7 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
|
|||
var head =
|
||||
(noDocType ? '' : '<!doctype html>\n') +
|
||||
'<html lang="en">\n' + (noDocType ? '' : '<head>\n' +
|
||||
'<title>' + Security.escapeHTML(padId) + '</title>\n' +
|
||||
'<title>' + Security.escapeHTML(padId) + '</title>\n' +
|
||||
'<meta charset="utf-8">\n' +
|
||||
'<style> * { font-family: arial, sans-serif;\n' +
|
||||
'font-size: 13px;\n' +
|
||||
|
@ -576,80 +515,7 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
|
|||
callback(null, head + html + foot);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function _encodeWhitespace(s) {
|
||||
return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c)
|
||||
{
|
||||
return "&#" +c.charCodeAt(0) + ";"
|
||||
});
|
||||
}
|
||||
|
||||
// copied from ACE
|
||||
|
||||
|
||||
function _processSpaces(s)
|
||||
{
|
||||
var doesWrap = true;
|
||||
if (s.indexOf("<") < 0 && !doesWrap)
|
||||
{
|
||||
// short-cut
|
||||
return s.replace(/ /g, ' ');
|
||||
}
|
||||
var parts = [];
|
||||
s.replace(/<[^>]*>?| |[^ <]+/g, function (m)
|
||||
{
|
||||
parts.push(m);
|
||||
});
|
||||
if (doesWrap)
|
||||
{
|
||||
var endOfLine = true;
|
||||
var beforeSpace = false;
|
||||
// last space in a run is normal, others are nbsp,
|
||||
// end of line is nbsp
|
||||
for (var i = parts.length - 1; i >= 0; i--)
|
||||
{
|
||||
var p = parts[i];
|
||||
if (p == " ")
|
||||
{
|
||||
if (endOfLine || beforeSpace) parts[i] = ' ';
|
||||
endOfLine = false;
|
||||
beforeSpace = true;
|
||||
}
|
||||
else if (p.charAt(0) != "<")
|
||||
{
|
||||
endOfLine = false;
|
||||
beforeSpace = false;
|
||||
}
|
||||
}
|
||||
// beginning of line is nbsp
|
||||
for (var i = 0; i < parts.length; i++)
|
||||
{
|
||||
var p = parts[i];
|
||||
if (p == " ")
|
||||
{
|
||||
parts[i] = ' ';
|
||||
break;
|
||||
}
|
||||
else if (p.charAt(0) != "<")
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < parts.length; i++)
|
||||
{
|
||||
var p = parts[i];
|
||||
if (p == " ")
|
||||
{
|
||||
parts[i] = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
return parts.join('');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// copied from ACE
|
||||
|
@ -676,3 +542,56 @@ function _findURLs(text)
|
|||
|
||||
return urls;
|
||||
}
|
||||
|
||||
|
||||
// copied from ACE
|
||||
function _processSpaces(s){
|
||||
var doesWrap = true;
|
||||
if (s.indexOf("<") < 0 && !doesWrap){
|
||||
// short-cut
|
||||
return s.replace(/ /g, ' ');
|
||||
}
|
||||
var parts = [];
|
||||
s.replace(/<[^>]*>?| |[^ <]+/g, function (m){
|
||||
parts.push(m);
|
||||
});
|
||||
if (doesWrap){
|
||||
var endOfLine = true;
|
||||
var beforeSpace = false;
|
||||
// last space in a run is normal, others are nbsp,
|
||||
// end of line is nbsp
|
||||
for (var i = parts.length - 1; i >= 0; i--){
|
||||
var p = parts[i];
|
||||
if (p == " "){
|
||||
if (endOfLine || beforeSpace) parts[i] = ' ';
|
||||
endOfLine = false;
|
||||
beforeSpace = true;
|
||||
}
|
||||
else if (p.charAt(0) != "<"){
|
||||
endOfLine = false;
|
||||
beforeSpace = false;
|
||||
}
|
||||
}
|
||||
// beginning of line is nbsp
|
||||
for (var i = 0; i < parts.length; i++){
|
||||
var p = parts[i];
|
||||
if (p == " "){
|
||||
parts[i] = ' ';
|
||||
break;
|
||||
}
|
||||
else if (p.charAt(0) != "<"){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < parts.length; i++){
|
||||
var p = parts[i];
|
||||
if (p == " "){
|
||||
parts[i] = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
return parts.join('');
|
||||
}
|
||||
|
|
292
src/node/utils/ExportTxt.js
Normal file
292
src/node/utils/ExportTxt.js
Normal file
|
@ -0,0 +1,292 @@
|
|||
/**
|
||||
* TXT export
|
||||
*/
|
||||
|
||||
/*
|
||||
* 2013 John McLear
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var async = require("async");
|
||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||
var padManager = require("../db/PadManager");
|
||||
var ERR = require("async-stacktrace");
|
||||
var Security = require('ep_etherpad-lite/static/js/security');
|
||||
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
var getPadPlainText = require('./ExportHelper').getPadPlainText;
|
||||
var _analyzeLine = require('./ExportHelper')._analyzeLine;
|
||||
|
||||
// This is slightly different than the HTML method as it passes the output to getTXTFromAText
|
||||
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;
|
||||
atext = revisionAtext;
|
||||
callback();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(null);
|
||||
}
|
||||
},
|
||||
|
||||
// convert atext to html
|
||||
|
||||
|
||||
function (callback)
|
||||
{
|
||||
html = getTXTFromAtext(pad, atext); // only this line is different to the HTML function
|
||||
callback(null);
|
||||
}],
|
||||
// run final callback
|
||||
|
||||
|
||||
function (err)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
callback(null, html);
|
||||
});
|
||||
}
|
||||
|
||||
exports.getPadTXT = getPadTXT;
|
||||
|
||||
|
||||
// This is different than the functionality provided in ExportHtml as it provides formatting
|
||||
// functionality that is designed specifically for TXT exports
|
||||
function getTXTFromAtext(pad, atext, authorColors)
|
||||
{
|
||||
var apool = pad.apool();
|
||||
var textLines = atext.text.slice(0, -1).split('\n');
|
||||
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||
|
||||
var tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
|
||||
var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
|
||||
var anumMap = {};
|
||||
var css = "";
|
||||
|
||||
props.forEach(function (propName, i)
|
||||
{
|
||||
var propTrueNum = apool.putAttrib([propName, true], true);
|
||||
if (propTrueNum >= 0)
|
||||
{
|
||||
anumMap[propTrueNum] = i;
|
||||
}
|
||||
});
|
||||
|
||||
function getLineTXT(text, attribs)
|
||||
{
|
||||
var propVals = [false, false, false];
|
||||
var ENTER = 1;
|
||||
var STAY = 2;
|
||||
var LEAVE = 0;
|
||||
|
||||
// Use order of tags (b/i/u) as order of nesting, for simplicity
|
||||
// and decent nesting. For example,
|
||||
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
|
||||
// becomes
|
||||
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
|
||||
var taker = Changeset.stringIterator(text);
|
||||
var assem = Changeset.stringAssembler();
|
||||
var openTags = [];
|
||||
|
||||
var idx = 0;
|
||||
|
||||
function processNextChars(numChars)
|
||||
{
|
||||
if (numChars <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
|
||||
idx += numChars;
|
||||
|
||||
while (iter.hasNext())
|
||||
{
|
||||
var o = iter.next();
|
||||
var propChanged = false;
|
||||
Changeset.eachAttribNumber(o.attribs, function (a)
|
||||
{
|
||||
if (a in anumMap)
|
||||
{
|
||||
var i = anumMap[a]; // i = 0 => bold, etc.
|
||||
if (!propVals[i])
|
||||
{
|
||||
propVals[i] = ENTER;
|
||||
propChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
propVals[i] = STAY;
|
||||
}
|
||||
}
|
||||
});
|
||||
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
|
||||
}
|
||||
}
|
||||
// now each member of propVal is in {false,LEAVE,ENTER,true}
|
||||
// according to what happens at start of span
|
||||
if (propChanged)
|
||||
{
|
||||
// leaving bold (e.g.) also leaves italics, etc.
|
||||
var left = false;
|
||||
for (var i = 0; i < propVals.length; i++)
|
||||
{
|
||||
var v = propVals[i];
|
||||
if (!left)
|
||||
{
|
||||
if (v === LEAVE)
|
||||
{
|
||||
left = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (v === true)
|
||||
{
|
||||
propVals[i] = STAY; // tag will be closed and re-opened
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tags2close = [];
|
||||
|
||||
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)
|
||||
{
|
||||
//emitCloseTag(i);
|
||||
tags2close.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < propVals.length; i++)
|
||||
{
|
||||
if (propVals[i] === ENTER || propVals[i] === STAY)
|
||||
{
|
||||
propVals[i] = true;
|
||||
}
|
||||
}
|
||||
// propVals is now all {true,false} again
|
||||
} // end if (propChanged)
|
||||
|
||||
var chars = o.chars;
|
||||
if (o.lines)
|
||||
{
|
||||
chars--; // exclude newline at end of line, if present
|
||||
}
|
||||
|
||||
var s = taker.take(chars);
|
||||
|
||||
// removes the characters with the code 12. Don't know where they come
|
||||
// from but they break the abiword parser and are completly useless
|
||||
// s = s.replace(String.fromCharCode(12), "");
|
||||
|
||||
// remove * from s, it's just not needed on a blank line.. This stops
|
||||
// plugins from being able to display * at the beginning of a line
|
||||
// s = s.replace("*", ""); // Then remove it
|
||||
|
||||
assem.append(s);
|
||||
} // end iteration over spans in line
|
||||
|
||||
var tags2close = [];
|
||||
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
|
||||
// only gain one level of nesting at once, can't change type
|
||||
// mid-list, etc.
|
||||
// People might use weird indenting, e.g. skip a level,
|
||||
// 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
|
||||
var lists = []; // e.g. [[1,'bullet'], [3,'bullet'], ...]
|
||||
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"){
|
||||
lineContent = "* " + lineContent; // add a bullet
|
||||
}
|
||||
if(line.listLevel > 0){
|
||||
for (var j = line.listLevel - 1; j >= 0; j--){
|
||||
pieces.push('\t');
|
||||
}
|
||||
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{
|
||||
pieces.push(lineContent, '\n');
|
||||
}
|
||||
}
|
||||
|
||||
return pieces.join('');
|
||||
}
|
||||
exports.getTXTFromAtext = getTXTFromAtext;
|
||||
|
||||
exports.getPadTXTDocument = function (padId, revNum, noDocType, callback)
|
||||
{
|
||||
padManager.getPad(padId, function (err, pad)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
getPadTXT(pad, revNum, function (err, html)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
callback(null, html);
|
||||
});
|
||||
});
|
||||
};
|
|
@ -26,7 +26,13 @@ function setPadHTML(pad, html, callback)
|
|||
var apiLogger = log4js.getLogger("ImportHtml");
|
||||
|
||||
// Parse the incoming HTML with jsdom
|
||||
var doc = jsdom(html.replace(/>\n+</g, '><'));
|
||||
try{
|
||||
var doc = jsdom(html.replace(/>\n+</g, '><'));
|
||||
}catch(e){
|
||||
apiLogger.warn("Error importing, possibly caused by malformed HTML");
|
||||
var doc = jsdom("<html><body><div>Error during import, possibly malformed HTML</div></body></html>");
|
||||
}
|
||||
|
||||
apiLogger.debug('html:');
|
||||
apiLogger.debug(html);
|
||||
|
||||
|
|
|
@ -125,11 +125,11 @@ function requestURIs(locations, method, headers, callback) {
|
|||
}
|
||||
|
||||
function completed() {
|
||||
var statuss = responses.map(function (x) {return x[0]});
|
||||
var headerss = responses.map(function (x) {return x[1]});
|
||||
var contentss = responses.map(function (x) {return x[2]});
|
||||
var statuss = responses.map(function (x) {return x[0];});
|
||||
var headerss = responses.map(function (x) {return x[1];});
|
||||
var contentss = responses.map(function (x) {return x[2];});
|
||||
callback(statuss, headerss, contentss);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -263,7 +263,7 @@ function getAceFile(callback) {
|
|||
var filename = item.match(/"([^"]*)"/)[1];
|
||||
var request = require('request');
|
||||
|
||||
var baseURI = 'http://localhost:' + settings.port
|
||||
var baseURI = 'http://localhost:' + settings.port;
|
||||
var resourceURI = baseURI + path.normalize(path.join('/static/', filename));
|
||||
resourceURI = resourceURI.replace(/\\/g, '/'); // Windows (safe generally?)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* The Settings Modul reads the settings out of settings.json and provides
|
||||
* The Settings Modul reads the settings out of settings.json and provides
|
||||
* this information to the other modules
|
||||
*/
|
||||
|
||||
|
@ -24,8 +24,10 @@ var os = require("os");
|
|||
var path = require('path');
|
||||
var argv = require('./Cli').argv;
|
||||
var npm = require("npm/lib/npm.js");
|
||||
var vm = require('vm');
|
||||
var jsonminify = require("jsonminify");
|
||||
var log4js = require("log4js");
|
||||
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
||||
|
||||
|
||||
/* Root path of the installation */
|
||||
exports.root = path.normalize(path.join(npm.dir, ".."));
|
||||
|
@ -33,7 +35,7 @@ exports.root = path.normalize(path.join(npm.dir, ".."));
|
|||
/**
|
||||
* The app title, visible e.g. in the browser window
|
||||
*/
|
||||
exports.title = "Etherpad Lite";
|
||||
exports.title = "Etherpad";
|
||||
|
||||
/**
|
||||
* The app favicon fully specified url, visible e.g. in the browser window
|
||||
|
@ -46,7 +48,7 @@ exports.faviconTimeslider = "../../" + exports.favicon;
|
|||
* The IP ep-lite should listen to
|
||||
*/
|
||||
exports.ip = "0.0.0.0";
|
||||
|
||||
|
||||
/**
|
||||
* The Port ep-lite should listen to
|
||||
*/
|
||||
|
@ -75,7 +77,7 @@ exports.dbSettings = { "filename" : path.join(exports.root, "dirty.db") };
|
|||
/**
|
||||
* The default Text of a new pad
|
||||
*/
|
||||
exports.defaultPadText = "Welcome to Etherpad Lite!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nEtherpad Lite on Github: http:\/\/j.mp/ep-lite\n";
|
||||
exports.defaultPadText = "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nEtherpad on Github: http:\/\/j.mp/ep-lite\n";
|
||||
|
||||
/**
|
||||
* A flag that requires any user to have a valid session (via the api) before accessing a pad
|
||||
|
@ -107,11 +109,26 @@ exports.abiword = null;
|
|||
*/
|
||||
exports.loglevel = "INFO";
|
||||
|
||||
/**
|
||||
* Disable IP logging
|
||||
*/
|
||||
exports.disableIPlogging = false;
|
||||
|
||||
/*
|
||||
* log4js appender configuration
|
||||
*/
|
||||
exports.logconfig = { appenders: [{ type: "console" }]};
|
||||
|
||||
/*
|
||||
* Session Key, do not sure this.
|
||||
*/
|
||||
exports.sessionKey = false;
|
||||
|
||||
/*
|
||||
* Trust Proxy, whether or not trust the x-forwarded-for header.
|
||||
*/
|
||||
exports.trustProxy = false;
|
||||
|
||||
/* This setting is used if you need authentication and/or
|
||||
* authorization. Note: /admin always requires authentication, and
|
||||
* either authorization by a module, or a user with is_admin set */
|
||||
|
@ -130,14 +147,12 @@ exports.abiwordAvailable = function()
|
|||
{
|
||||
return "no";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
exports.reloadSettings = function reloadSettings() {
|
||||
// Discover where the settings file lives
|
||||
var settingsFilename = argv.settings || "settings.json";
|
||||
settingsFilename = path.resolve(path.join(root, settingsFilename));
|
||||
settingsFilename = path.resolve(path.join(exports.root, settingsFilename));
|
||||
|
||||
var settingsStr;
|
||||
try{
|
||||
|
@ -151,7 +166,8 @@ exports.reloadSettings = function reloadSettings() {
|
|||
var settings;
|
||||
try {
|
||||
if(settingsStr) {
|
||||
settings = vm.runInContext('exports = '+settingsStr, vm.createContext(), "settings.json");
|
||||
settingsStr = jsonminify(settingsStr).replace(",]","]").replace(",}","}");
|
||||
settings = JSON.parse(settingsStr);
|
||||
}
|
||||
}catch(e){
|
||||
console.error('There was an error processing your settings.json file: '+e.message);
|
||||
|
@ -179,15 +195,20 @@ exports.reloadSettings = function reloadSettings() {
|
|||
console.warn("Unknown Setting: '" + i + "'. This setting doesn't exist or it was removed");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
log4js.configure(exports.logconfig);//Configure the logging appenders
|
||||
log4js.setGlobalLogLevel(exports.loglevel);//set loglevel
|
||||
log4js.replaceConsole();
|
||||
|
||||
if(exports.dbType === "dirty"){
|
||||
console.warn("DirtyDB is used. This is fine for testing but not recommended for production.")
|
||||
if(!exports.sessionKey){ // If the secretKey isn't set we also create yet another unique value here
|
||||
exports.sessionKey = randomString(32);
|
||||
console.warn("You need to set a sessionKey value in settings.json, this will allow your users to reconnect to your Etherpad Instance if your instance restarts");
|
||||
}
|
||||
}
|
||||
|
||||
if(exports.dbType === "dirty"){
|
||||
console.warn("DirtyDB is used. This is fine for testing but not recommended for production.");
|
||||
}
|
||||
};
|
||||
|
||||
// initially load settings
|
||||
exports.reloadSettings();
|
||||
|
|
|
@ -23,7 +23,7 @@ var util = require('util');
|
|||
var settings = require('./Settings');
|
||||
var semver = require('semver');
|
||||
|
||||
var existsSync = (semver.satisfies(process.version, '>=0.8.0')) ? fs.existsSync : path.existsSync
|
||||
var existsSync = (semver.satisfies(process.version, '>=0.8.0')) ? fs.existsSync : path.existsSync;
|
||||
|
||||
var CACHE_DIR = path.normalize(path.join(settings.root, 'var/'));
|
||||
CACHE_DIR = existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
|
||||
|
@ -133,7 +133,7 @@ CachingMiddleware.prototype = new function () {
|
|||
old_res.write = res.write;
|
||||
old_res.end = res.end;
|
||||
res.write = function(data, encoding) {};
|
||||
res.end = function(data, encoding) { respond() };
|
||||
res.end = function(data, encoding) { respond(); };
|
||||
} else {
|
||||
res.writeHead(status, headers);
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ CachingMiddleware.prototype = new function () {
|
|||
} else if (req.method == 'GET') {
|
||||
var readStream = fs.createReadStream(pathStr);
|
||||
res.writeHead(statusCode, headers);
|
||||
util.pump(readStream, res);
|
||||
readStream.pipe(res);
|
||||
} else {
|
||||
res.writeHead(statusCode, headers);
|
||||
res.end();
|
||||
|
|
|
@ -68,7 +68,7 @@ PadDiff.prototype._isClearAuthorship = function(changeset){
|
|||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
PadDiff.prototype._createClearAuthorship = function(rev, callback){
|
||||
var self = this;
|
||||
|
@ -84,7 +84,7 @@ PadDiff.prototype._createClearAuthorship = function(rev, callback){
|
|||
|
||||
callback(null, changeset);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
PadDiff.prototype._createClearStartAtext = function(rev, callback){
|
||||
var self = this;
|
||||
|
@ -107,7 +107,7 @@ PadDiff.prototype._createClearStartAtext = function(rev, callback){
|
|||
callback(null, newAText);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
PadDiff.prototype._getChangesetsInBulk = function(startRev, count, callback) {
|
||||
var self = this;
|
||||
|
@ -124,7 +124,7 @@ PadDiff.prototype._getChangesetsInBulk = function(startRev, count, callback) {
|
|||
async.forEach(revisions, function(rev, callback){
|
||||
self._pad.getRevision(rev, function(err, revision){
|
||||
if(err){
|
||||
return callback(err)
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var arrayNum = rev-startRev;
|
||||
|
@ -137,7 +137,7 @@ PadDiff.prototype._getChangesetsInBulk = function(startRev, count, callback) {
|
|||
}, function(err){
|
||||
callback(err, changesets, authors);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
PadDiff.prototype._addAuthors = function(authors) {
|
||||
var self = this;
|
||||
|
@ -147,7 +147,7 @@ PadDiff.prototype._addAuthors = function(authors) {
|
|||
self._authors.push(author);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
PadDiff.prototype._createDiffAtext = function(callback) {
|
||||
var self = this;
|
||||
|
@ -219,7 +219,7 @@ PadDiff.prototype._createDiffAtext = function(callback) {
|
|||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
PadDiff.prototype.getHtml = function(callback){
|
||||
//cache the html
|
||||
|
@ -279,7 +279,7 @@ PadDiff.prototype.getAuthors = function(callback){
|
|||
} else {
|
||||
callback(null, self._authors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
PadDiff.prototype._extendChangesetWithAuthor = function(changeset, author, apool) {
|
||||
//unpack
|
||||
|
@ -312,7 +312,7 @@ PadDiff.prototype._extendChangesetWithAuthor = function(changeset, author, apool
|
|||
|
||||
//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.
|
||||
PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
|
||||
|
@ -331,14 +331,6 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
|
|||
}
|
||||
}
|
||||
|
||||
function lines_length() {
|
||||
if ((typeof lines.length) == "number") {
|
||||
return lines.length;
|
||||
} else {
|
||||
return lines.length();
|
||||
}
|
||||
}
|
||||
|
||||
function alines_get(idx) {
|
||||
if (alines.get) {
|
||||
return alines.get(idx);
|
||||
|
@ -347,14 +339,6 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
|
|||
}
|
||||
}
|
||||
|
||||
function alines_length() {
|
||||
if ((typeof alines.length) == "number") {
|
||||
return alines.length;
|
||||
} else {
|
||||
return alines.length();
|
||||
}
|
||||
}
|
||||
|
||||
var curLine = 0;
|
||||
var curChar = 0;
|
||||
var curLineOpIter = null;
|
||||
|
@ -463,7 +447,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
|
|||
// If the text this operator applies to is only a star, than this is a false positive and should be ignored
|
||||
if (csOp.attribs && textBank != "*") {
|
||||
var deletedAttrib = apool.putAttrib(["removed", true]);
|
||||
var authorAttrib = apool.putAttrib(["author", ""]);;
|
||||
var authorAttrib = apool.putAttrib(["author", ""]);
|
||||
|
||||
attribKeys.length = 0;
|
||||
attribValues.length = 0;
|
||||
|
@ -473,7 +457,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
|
|||
|
||||
if(apool.getAttribKey(n) === "author"){
|
||||
authorAttrib = n;
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
var undoBackToAttribs = cachedStrFunc(function (attribs) {
|
||||
|
|
|
@ -46,7 +46,6 @@
|
|||
, "Changeset.js"
|
||||
, "ChangesetUtils.js"
|
||||
, "skiplist.js"
|
||||
, "virtual_lines.js"
|
||||
, "cssmanager.js"
|
||||
, "colorutils.js"
|
||||
, "undomodule.js"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue