summaryrefslogtreecommitdiff
path: root/src/node
diff options
context:
space:
mode:
authorStefan <mu.stefan@googlemail.com>2015-04-11 12:10:37 +0200
committerStefan <mu.stefan@googlemail.com>2015-04-11 12:10:37 +0200
commitaa0d14c7d71da3d3c4f123e1577848c026bccf0b (patch)
tree19cc760c2aa04ab57eb23e500dc442c0a901a7d1 /src/node
parent573a912e4f1b481fca8f3c8146972e78f76278e2 (diff)
parentcc34f4e325830f798321b8152095c4dccd6b465f (diff)
downloadetherpad-lite-aa0d14c7d71da3d3c4f123e1577848c026bccf0b.zip
Merge branch 'master' of git://github.com/ether/etherpad-lite into create_pad_special_characters
Diffstat (limited to 'src/node')
-rw-r--r--src/node/db/API.js274
-rw-r--r--src/node/db/AuthorManager.js1
-rw-r--r--src/node/db/Pad.js33
-rw-r--r--src/node/db/SecurityManager.js1
-rw-r--r--src/node/db/SessionManager.js10
-rw-r--r--src/node/db/SessionStore.js2
-rw-r--r--src/node/eejs/index.js2
-rw-r--r--src/node/handler/APIHandler.js102
-rw-r--r--src/node/handler/ExportHandler.js44
-rw-r--r--src/node/handler/ImportHandler.js194
-rw-r--r--src/node/handler/PadMessageHandler.js230
-rw-r--r--src/node/handler/SocketIORouter.js9
-rw-r--r--src/node/hooks/express.js22
-rw-r--r--src/node/hooks/express/adminplugins.js20
-rw-r--r--src/node/hooks/express/adminsettings.js5
-rw-r--r--src/node/hooks/express/importexport.js2
-rw-r--r--src/node/hooks/express/padreadonly.js1
-rw-r--r--src/node/hooks/express/socketio.js56
-rw-r--r--src/node/hooks/express/static.js5
-rw-r--r--src/node/hooks/express/swagger.js17
-rw-r--r--src/node/hooks/express/tests.js11
-rw-r--r--src/node/hooks/express/webaccess.js1
-rw-r--r--src/node/hooks/i18n.js1
-rw-r--r--src/node/utils/Abiword.js1
-rw-r--r--src/node/utils/ExportDokuWiki.js350
-rw-r--r--src/node/utils/ExportEtherpad.js79
-rw-r--r--src/node/utils/ExportHelper.js7
-rw-r--r--src/node/utils/ExportHtml.js207
-rw-r--r--src/node/utils/ExportTxt.js6
-rw-r--r--src/node/utils/ImportEtherpad.js83
-rw-r--r--src/node/utils/ImportHtml.js20
-rw-r--r--src/node/utils/Minify.js9
-rw-r--r--src/node/utils/RemoteAddress.js1
-rw-r--r--src/node/utils/Settings.js74
-rw-r--r--src/node/utils/caching_middleware.js1
-rw-r--r--src/node/utils/padDiff.js16
-rw-r--r--src/node/utils/tar.json4
-rw-r--r--src/node/utils/toolbar.js14
38 files changed, 1229 insertions, 686 deletions
diff --git a/src/node/db/API.js b/src/node/db/API.js
index 79f5fbeb..97d5162d 100644
--- a/src/node/db/API.js
+++ b/src/node/db/API.js
@@ -263,7 +263,7 @@ exports.getText = function(padID, rev, callback)
{
if(ERR(err, callback)) return;
- data = {text: atext.text};
+ var data = {text: atext.text};
callback(null, data);
})
@@ -368,7 +368,7 @@ exports.getHTML = function(padID, rev, callback)
if(ERR(err, callback)) return;
html = "<!DOCTYPE HTML><html><body>" +html; // adds HTML head
html += "</body></html>";
- data = {html: html};
+ var data = {html: html};
callback(null, data);
});
}
@@ -380,7 +380,7 @@ exports.getHTML = function(padID, rev, callback)
if(ERR(err, callback)) return;
html = "<!DOCTYPE HTML><html><body>" +html; // adds HTML head
html += "</body></html>";
- data = {html: html};
+ var data = {html: html};
callback(null, data);
});
}
@@ -410,11 +410,16 @@ exports.setHTML = function(padID, html, callback)
if(ERR(err, callback)) return;
// add a new changeset with the new html to the pad
- importHtml.setPadHTML(pad, cleanText(html), callback);
-
- //update the clients on the pad
- padMessageHandler.updatePadClients(pad, callback);
-
+ importHtml.setPadHTML(pad, cleanText(html), function(e){
+ if(e){
+ callback(new customError("HTML is malformed","apierror"));
+ return;
+ }else{
+ //update the clients on the pad
+ padMessageHandler.updatePadClients(pad, callback);
+ return;
+ }
+ });
});
}
@@ -427,8 +432,8 @@ getChatHistory(padId, start, end), returns a part of or the whole chat-history o
Example returns:
-{"code":0,"message":"ok","data":{"messages":[{"text":"foo","userId":"a.foo","time":1359199533759,"userName":"test"},
- {"text":"bar","userId":"a.foo","time":1359199534622,"userName":"test"}]}}
+{"code":0,"message":"ok","data":{"messages":[{"text":"foo","authorID":"a.foo","time":1359199533759,"userName":"test"},
+ {"text":"bar","authorID":"a.foo","time":1359199534622,"userName":"test"}]}}
{code: 1, message:"start is higher or equal to the current chatHead", data: null}
@@ -489,6 +494,33 @@ exports.getChatHistory = function(padID, start, end, callback)
});
}
+/**
+appendChatMessage(padID, text, authorID, time), creates a chat message for the pad id, time is a timestamp
+
+Example returns:
+
+{code: 0, message:"ok", data: null
+{code: 1, message:"padID does not exist", data: null}
+*/
+exports.appendChatMessage = function(padID, text, authorID, time, callback)
+{
+ //text is required
+ if(typeof text != "string")
+ {
+ callback(new customError("text is no string","apierror"));
+ return;
+ }
+
+ //get the pad
+ getPadSafe(padID, true, function(err, pad)
+ {
+ if(ERR(err, callback)) return;
+
+ pad.appendChatMessage(text, authorID, parseInt(time));
+ callback();
+ });
+}
+
/*****************/
/**PAD FUNCTIONS */
/*****************/
@@ -513,6 +545,117 @@ exports.getRevisionsCount = function(padID, callback)
}
/**
+getSavedRevisionsCount(padID) returns the number of saved revisions of this pad
+
+Example returns:
+
+{code: 0, message:"ok", data: {savedRevisions: 42}}
+{code: 1, message:"padID does not exist", data: null}
+*/
+exports.getSavedRevisionsCount = function(padID, callback)
+{
+ //get the pad
+ getPadSafe(padID, true, function(err, pad)
+ {
+ if(ERR(err, callback)) return;
+
+ callback(null, {savedRevisions: pad.getSavedRevisionsNumber()});
+ });
+}
+
+/**
+listSavedRevisions(padID) returns the list of saved revisions of this pad
+
+Example returns:
+
+{code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}}
+{code: 1, message:"padID does not exist", data: null}
+*/
+exports.listSavedRevisions = function(padID, callback)
+{
+ //get the pad
+ getPadSafe(padID, true, function(err, pad)
+ {
+ if(ERR(err, callback)) return;
+
+ callback(null, {savedRevisions: pad.getSavedRevisionsList()});
+ });
+}
+
+/**
+saveRevision(padID) returns the list of saved revisions of this pad
+
+Example returns:
+
+{code: 0, message:"ok", data: null}
+{code: 1, message:"padID does not exist", data: null}
+*/
+exports.saveRevision = 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 negativ number
+ if(rev !== undefined && rev < 0)
+ {
+ callback(new customError("rev is a negativ number","apierror"));
+ return;
+ }
+
+ //ensure this is not a float value
+ if(rev !== undefined && !is_int(rev))
+ {
+ callback(new customError("rev is a float value","apierror"));
+ return;
+ }
+
+ //get the pad
+ getPadSafe(padID, true, function(err, pad)
+ {
+ if(ERR(err, callback)) return;
+
+ //the client asked for a special revision
+ if(rev !== undefined)
+ {
+ //check if this is a valid revision
+ if(rev > pad.getHeadRevisionNumber())
+ {
+ callback(new customError("rev is higher than the head revision of the pad","apierror"));
+ return;
+ }
+ } else {
+ rev = pad.getHeadRevisionNumber();
+ }
+
+ authorManager.createAuthor('API', function(err, author) {
+ if(ERR(err, callback)) return;
+
+ pad.addSavedRevision(rev, author.authorID, 'Saved through API call');
+ callback();
+ });
+ });
+}
+
+/**
getLastEdited(padID) returns the timestamp of the last revision of the pad
Example returns:
@@ -584,6 +727,117 @@ exports.deletePad = function(padID, callback)
pad.remove(callback);
});
}
+/**
+ restoreRevision(padID, [rev]) Restores revision from past as new changeset
+
+ Example returns:
+
+ {code:0, message:"ok", data:null}
+ {code: 1, message:"padID does not exist", data: null}
+ */
+exports.restoreRevision = function (padID, rev, callback)
+{
+ var Changeset = require("ep_etherpad-lite/static/js/Changeset");
+ var padMessage = require("ep_etherpad-lite/node/handler/PadMessageHandler.js");
+
+ //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 negativ number
+ if (rev !== undefined && rev < 0)
+ {
+ callback(new customError("rev is a negativ number", "apierror"));
+ return;
+ }
+
+ //ensure this is not a float value
+ if (rev !== undefined && !is_int(rev))
+ {
+ callback(new customError("rev is a float value", "apierror"));
+ return;
+ }
+
+ //get the pad
+ getPadSafe(padID, true, function (err, pad)
+ {
+ if (ERR(err, callback)) return;
+
+
+ //check if this is a valid revision
+ if (rev > pad.getHeadRevisionNumber())
+ {
+ callback(new customError("rev is higher than the head revision of the pad", "apierror"));
+ return;
+ }
+
+ pad.getInternalRevisionAText(rev, function (err, atext)
+ {
+ if (ERR(err, callback)) return;
+
+ var oldText = pad.text();
+ atext.text += "\n";
+ function eachAttribRun(attribs, func)
+ {
+ var attribsIter = Changeset.opIterator(attribs);
+ var textIndex = 0;
+ var newTextStart = 0;
+ var newTextEnd = atext.text.length;
+ while (attribsIter.hasNext())
+ {
+ var op = attribsIter.next();
+ var nextIndex = textIndex + op.chars;
+ if (!(nextIndex <= newTextStart || textIndex >= newTextEnd))
+ {
+ func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
+ }
+ textIndex = nextIndex;
+ }
+ }
+
+ // create a new changeset with a helper builder object
+ var builder = Changeset.builder(oldText.length);
+
+ // assemble each line into the builder
+ eachAttribRun(atext.attribs, function (start, end, attribs)
+ {
+ builder.insert(atext.text.substring(start, end), attribs);
+ });
+
+ var lastNewlinePos = oldText.lastIndexOf('\n');
+ if (lastNewlinePos < 0)
+ {
+ builder.remove(oldText.length - 1, 0);
+ } else
+ {
+ builder.remove(lastNewlinePos, oldText.match(/\n/g).length - 1);
+ builder.remove(oldText.length - lastNewlinePos - 1, 0);
+ }
+
+ var changeset = builder.toString();
+
+ //append the changeset
+ pad.appendRevision(changeset);
+ //
+ padMessage.updatePadClients(pad, function ()
+ {
+ });
+ callback(null, null);
+ });
+
+ });
+};
/**
copyPad(sourceID, destinationID[, force=false]) copies a pad. If force is true,
diff --git a/src/node/db/AuthorManager.js b/src/node/db/AuthorManager.js
index 5ba608e9..e0f569ef 100644
--- a/src/node/db/AuthorManager.js
+++ b/src/node/db/AuthorManager.js
@@ -21,7 +21,6 @@
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;
diff --git a/src/node/db/Pad.js b/src/node/db/Pad.js
index 4670696a..53847600 100644
--- a/src/node/db/Pad.js
+++ b/src/node/db/Pad.js
@@ -54,6 +54,21 @@ Pad.prototype.getHeadRevisionNumber = function getHeadRevisionNumber() {
return this.head;
};
+Pad.prototype.getSavedRevisionsNumber = function getSavedRevisionsNumber() {
+ return this.savedRevisions.length;
+};
+
+Pad.prototype.getSavedRevisionsList = function getSavedRevisionsList() {
+ var savedRev = new Array();
+ for(var rev in this.savedRevisions){
+ savedRev.push(this.savedRevisions[rev].revNum);
+ }
+ savedRev.sort(function(a, b) {
+ return a - b;
+ });
+ return savedRev;
+};
+
Pad.prototype.getPublicStatus = function getPublicStatus() {
return this.publicStatus;
};
@@ -135,7 +150,7 @@ Pad.prototype.getRevisionDate = function getRevisionDate(revNum, callback) {
Pad.prototype.getAllAuthors = function getAllAuthors() {
var authors = [];
- for(key in this.pool.numToAttrib)
+ for(var key in this.pool.numToAttrib)
{
if(this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "")
{
@@ -461,7 +476,6 @@ Pad.prototype.copy = function copy(destinationID, force, 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;
@@ -470,9 +484,9 @@ Pad.prototype.copy = function copy(destinationID, force, callback) {
{
if (!force)
{
- console.log("erroring out without force");
+ console.error("erroring out without force");
callback(new customError("destinationID already exists","apierror"));
- console.log("erroring out without force - after");
+ console.error("erroring out without force - after");
return;
}
else // exists and forcing
@@ -521,12 +535,9 @@ Pad.prototype.copy = function copy(destinationID, force, callback) {
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);
});
@@ -538,10 +549,8 @@ Pad.prototype.copy = function copy(destinationID, force, callback) {
function(callback)
{
var authorIDs = _this.getAllAuthors();
-
authorIDs.forEach(function (authorID)
{
- console.log("authors");
authorManager.addPad(authorID, destinationID);
});
@@ -555,7 +564,9 @@ Pad.prototype.copy = function copy(destinationID, force, callback) {
if(destGroupID) db.setSub("group:" + destGroupID, ["pads", destinationID], 1);
// Initialize the new pad (will update the listAllPads cache)
- padManager.getPad(destinationID, null, callback)
+ setTimeout(function(){
+ padManager.getPad(destinationID, null, callback) // this runs too early.
+ },10);
}
// series
], function(err)
@@ -690,7 +701,7 @@ Pad.prototype.isPasswordProtected = function isPasswordProtected() {
Pad.prototype.addSavedRevision = function addSavedRevision(revNum, savedById, label) {
//if this revision is already saved, return silently
for(var i in this.savedRevisions){
- if(this.savedRevisions.revNum === revNum){
+ if(this.savedRevisions[i] && this.savedRevisions[i].revNum === revNum){
return;
}
}
diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.js
index df3c3826..6fae57ff 100644
--- a/src/node/db/SecurityManager.js
+++ b/src/node/db/SecurityManager.js
@@ -20,7 +20,6 @@
var ERR = require("async-stacktrace");
-var db = require("./DB").db;
var async = require("async");
var authorManager = require("./AuthorManager");
var padManager = require("./PadManager");
diff --git a/src/node/db/SessionManager.js b/src/node/db/SessionManager.js
index 71315adc..f8000e47 100644
--- a/src/node/db/SessionManager.js
+++ b/src/node/db/SessionManager.js
@@ -351,7 +351,15 @@ function listSessionsWithDBKey (dbkey, callback)
{
exports.getSessionInfo(sessionID, function(err, sessionInfo)
{
- if(ERR(err, callback)) return;
+ if (err == "apierror: sessionID does not exist")
+ {
+ console.warn("Found bad session " + sessionID + " in " + dbkey + ".");
+ }
+ else if(ERR(err, callback))
+ {
+ return;
+ }
+
sessions[sessionID] = sessionInfo;
callback();
});
diff --git a/src/node/db/SessionStore.js b/src/node/db/SessionStore.js
index 52a504f1..5c45ddb3 100644
--- a/src/node/db/SessionStore.js
+++ b/src/node/db/SessionStore.js
@@ -5,8 +5,6 @@
*/
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");
diff --git a/src/node/eejs/index.js b/src/node/eejs/index.js
index 48185d80..30f5a442 100644
--- a/src/node/eejs/index.js
+++ b/src/node/eejs/index.js
@@ -71,7 +71,7 @@ exports.begin_define_block = function (name) {
}
exports.end_define_block = function () {
- content = exports.end_capture();
+ var content = exports.end_capture();
return content;
}
diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js
index 273a58a6..b4d24201 100644
--- a/src/node/handler/APIHandler.js
+++ b/src/node/handler/APIHandler.js
@@ -345,10 +345,109 @@ var version =
, "getChatHistory" : ["padID", "start", "end"]
, "getChatHead" : ["padID"]
}
+, "1.2.11":
+ { "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"]
+ , "getSavedRevisionsCount" : ["padID"]
+ , "listSavedRevisions" : ["padID"]
+ , "saveRevision" : ["padID", "rev"]
+ , "getRevisionChangeset" : ["padID", "rev"]
+ , "getLastEdited" : ["padID"]
+ , "deletePad" : ["padID"]
+ , "copyPad" : ["sourceID", "destinationID", "force"]
+ , "movePad" : ["sourceID", "destinationID", "force"]
+ , "getReadOnlyID" : ["padID"]
+ , "getPadID" : ["roID"]
+ , "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"]
+ , "restoreRevision" : ["padID", "rev"]
+ }
+, "1.2.12":
+ { "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"]
+ , "getSavedRevisionsCount" : ["padID"]
+ , "listSavedRevisions" : ["padID"]
+ , "saveRevision" : ["padID", "rev"]
+ , "getRevisionChangeset" : ["padID", "rev"]
+ , "getLastEdited" : ["padID"]
+ , "deletePad" : ["padID"]
+ , "copyPad" : ["sourceID", "destinationID", "force"]
+ , "movePad" : ["sourceID", "destinationID", "force"]
+ , "getReadOnlyID" : ["padID"]
+ , "getPadID" : ["roID"]
+ , "setPublicStatus" : ["padID", "publicStatus"]
+ , "getPublicStatus" : ["padID"]
+ , "setPassword" : ["padID", "password"]
+ , "isPasswordProtected" : ["padID"]
+ , "listAuthorsOfPad" : ["padID"]
+ , "padUsersCount" : ["padID"]
+ , "getAuthorName" : ["authorID"]
+ , "padUsers" : ["padID"]
+ , "sendClientsMessage" : ["padID", "msg"]
+ , "listAllGroups" : []
+ , "checkToken" : []
+ , "appendChatMessage" : ["padID", "text", "authorID", "time"]
+ , "getChatHistory" : ["padID"]
+ , "getChatHistory" : ["padID", "start", "end"]
+ , "getChatHead" : ["padID"]
+ , "restoreRevision" : ["padID", "rev"]
+ }
};
// set the latest available API version here
-exports.latestApiVersion = '1.2.10';
+exports.latestApiVersion = '1.2.12';
// exports the versions so it can be used by the new Swagger endpoint
exports.version = version;
@@ -404,6 +503,7 @@ exports.handle = function(apiVersion, functionName, fields, req, res)
if(fields["apikey"] != apikey.trim())
{
+ res.statusCode = 401;
res.send({code: 4, message: "no or wrong API Key", data: null});
return;
}
diff --git a/src/node/handler/ExportHandler.js b/src/node/handler/ExportHandler.js
index 5bedcce2..0654deb4 100644
--- a/src/node/handler/ExportHandler.js
+++ b/src/node/handler/ExportHandler.js
@@ -4,6 +4,7 @@
/*
* 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
+ * 2014 John McLear (Etherpad Foundation / McLear Ltd)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,8 +22,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 exportEtherpad = require("../utils/ExportEtherpad");
var async = require("async");
var fs = require("fs");
var settings = require('../utils/Settings');
@@ -54,14 +54,20 @@ exports.doExport = function(req, res, padId, type)
// if fileName is set then set it to the padId, note that fileName is returned as an array.
if(hookFileName.length) fileName = hookFileName;
-
//tell the browser that this is a downloadable file
res.attachment(fileName + "." + type);
//if this is a plain text export, we can do this directly
// We have to over engineer this because tabs are stored as attributes and not plain text
-
- if(type == "txt")
+ if(type == "etherpad"){
+ exportEtherpad.getPadRaw(padId, function(err, pad){
+ if(!err){
+ res.send(pad);
+ // return;
+ }
+ });
+ }
+ else if(type == "txt")
{
var txt;
var randNum;
@@ -129,26 +135,6 @@ exports.doExport = function(req, res, padId, type)
if(err && err != "stop") ERR(err);
})
}
- else if(type == 'dokuwiki')
- {
- var randNum;
- var srcFile, destFile;
-
- async.series([
- //render the dokuwiki document
- function(callback)
- {
- exportdokuwiki.getPadDokuWikiDocument(padId, req.params.rev, function(err, dokuwiki)
- {
- res.send(dokuwiki);
- callback("stop");
- });
- },
- ], function(err)
- {
- if(err && err != "stop") throw err;
- });
- }
else
{
var html;
@@ -172,8 +158,12 @@ exports.doExport = function(req, res, padId, type)
//if this is a html export, we can send this from here directly
if(type == "html")
{
- res.send(html);
- callback("stop");
+ // do any final changes the plugin might want to make cake
+ hooks.aCallFirst("exportHTMLSend", html, function(err, newHTML){
+ if(newHTML.length) html = newHTML;
+ res.send(html);
+ callback("stop");
+ });
}
else //write the html export to a file
{
diff --git a/src/node/handler/ImportHandler.js b/src/node/handler/ImportHandler.js
index 60fa5ffb..2dad8b3d 100644
--- a/src/node/handler/ImportHandler.js
+++ b/src/node/handler/ImportHandler.js
@@ -5,6 +5,7 @@
/*
* 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
* 2012 Iván Eixarch
+ * 2014 John McLear (Etherpad Foundation / McLear Ltd)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +30,7 @@ var ERR = require("async-stacktrace")
, formidable = require('formidable')
, os = require("os")
, importHtml = require("../utils/ImportHtml")
+ , importEtherpad = require("../utils/ImportEtherpad")
, log4js = require("log4js")
, hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
@@ -53,7 +55,8 @@ exports.doImport = function(req, res, padId)
var srcFile, destFile
, pad
, text
- , importHandledByPlugin;
+ , importHandledByPlugin
+ , directDatabaseAccess;
var randNum = Math.floor(Math.random()*0xFFFFFFFF);
@@ -83,7 +86,7 @@ exports.doImport = function(req, res, padId)
//this allows us to accept source code files like .c or .java
function(callback) {
var fileEnding = path.extname(srcFile).toLowerCase()
- , knownFileEndings = [".txt", ".doc", ".docx", ".pdf", ".odt", ".html", ".htm"]
+ , knownFileEndings = [".txt", ".doc", ".docx", ".pdf", ".odt", ".html", ".htm", ".etherpad"]
, fileEndingKnown = (knownFileEndings.indexOf(fileEnding) > -1);
//if the file ending is known, continue as normal
@@ -92,9 +95,14 @@ exports.doImport = function(req, res, padId)
}
//we need to rename this file with a .txt ending
else {
- var oldSrcFile = srcFile;
- srcFile = path.join(path.dirname(srcFile),path.basename(srcFile, fileEnding)+".txt");
- fs.rename(oldSrcFile, srcFile, callback);
+ if(settings.allowUnknownFileEnds === true){
+ var oldSrcFile = srcFile;
+ srcFile = path.join(path.dirname(srcFile),path.basename(srcFile, fileEnding)+".txt");
+ fs.rename(oldSrcFile, srcFile, callback);
+ }else{
+ console.warn("Not allowing unknown file type to be imported", fileEnding);
+ callback("uploadFailed");
+ }
}
},
function(callback){
@@ -111,11 +119,38 @@ exports.doImport = function(req, res, padId)
}
});
},
+ function(callback) {
+ var fileEnding = path.extname(srcFile).toLowerCase()
+ var fileIsEtherpad = (fileEnding === ".etherpad");
+
+ if(fileIsEtherpad){
+ // we do this here so we can see if the pad has quit ea few edits
+ padManager.getPad(padId, function(err, _pad){
+ var headCount = _pad.head;
+ if(headCount >= 10){
+ apiLogger.warn("Direct database Import attempt of a pad that already has content, we wont be doing this")
+ return callback("padHasData");
+ }else{
+ fs.readFile(srcFile, "utf8", function(err, _text){
+ directDatabaseAccess = true;
+ importEtherpad.setPadRaw(padId, _text, function(err){
+ callback();
+ });
+ });
+ }
+ });
+ }else{
+ callback();
+ }
+ },
//convert file to html
function(callback) {
- if(!importHandledByPlugin){
+ if(!importHandledByPlugin || !directDatabaseAccess){
var fileEnding = path.extname(srcFile).toLowerCase();
var fileIsHTML = (fileEnding === ".html" || fileEnding === ".htm");
+ var fileIsTXT = (fileEnding === ".txt");
+ if (fileIsTXT) abiword = false; // Don't use abiword for text files
+ // See https://github.com/ether/etherpad-lite/issues/2572
if (abiword && !fileIsHTML) {
abiword.convertFile(srcFile, destFile, "htm", function(err) {
//catch convert errors
@@ -136,24 +171,28 @@ exports.doImport = function(req, res, padId)
},
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 (!abiword){
+ if(!directDatabaseAccess) {
+ // 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");
- }
- });
+ if (isAscii) {
+ callback();
+ } else {
+ callback("uploadFailed");
+ }
+ });
+ }else{
+ callback();
+ }
} else {
callback();
}
@@ -170,66 +209,101 @@ exports.doImport = function(req, res, padId)
//read the text
function(callback) {
- 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>-->");
+ if(!directDatabaseAccess){
+ 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){
- setTimeout(function() {callback();}, 100);
- } else {
- callback();
- }
- });
+ //node on windows has a delay on releasing of the file lock.
+ //We add a 100ms delay to work around this
+ if(os.type().indexOf("Windows") > -1){
+ setTimeout(function() {callback();}, 100);
+ } else {
+ callback();
+ }
+ });
+ }else{
+ callback();
+ }
},
//change text of the pad and broadcast the changeset
function(callback) {
- var fileEnding = path.extname(srcFile).toLowerCase();
- if (abiword || fileEnding == ".htm" || fileEnding == ".html") {
- try{
- importHtml.setPadHTML(pad, text);
- }catch(e){
- apiLogger.warn("Error importing, possibly caused by malformed HTML");
+ if(!directDatabaseAccess){
+ var fileEnding = path.extname(srcFile).toLowerCase();
+ if (abiword || fileEnding == ".htm" || fileEnding == ".html") {
+ importHtml.setPadHTML(pad, text, function(e){
+ if(e) apiLogger.warn("Error importing, possibly caused by malformed HTML");
+ });
+ } else {
+ pad.setText(text);
}
- } else {
- pad.setText(text);
}
- padMessageHandler.updatePadClients(pad, callback);
+
+ // Load the Pad into memory then brodcast updates to all clients
+ padManager.unloadPad(padId);
+ padManager.getPad(padId, function(err, _pad){
+ var pad = _pad;
+ padManager.unloadPad(padId);
+ // direct Database Access means a pad user should perform a switchToPad
+ // and not attempt to recieve updated pad data..
+ if(!directDatabaseAccess){
+ padMessageHandler.updatePadClients(pad, function(){
+ callback();
+ });
+ }else{
+ callback();
+ }
+ });
+
},
//clean up temporary files
function(callback) {
- //for node < 0.7 compatible
- var fileExists = fs.exists || path.exists;
- async.parallel([
- function(callback){
- fileExists (srcFile, function(exist) { (exist)? fs.unlink(srcFile, callback): callback(); });
- },
- function(callback){
- fileExists (destFile, function(exist) { (exist)? fs.unlink(destFile, callback): callback(); });
- }
- ], callback);
+ if(!directDatabaseAccess){
+ //for node < 0.7 compatible
+ var fileExists = fs.exists || path.exists;
+ async.parallel([
+ function(callback){
+ fileExists (srcFile, function(exist) { (exist)? fs.unlink(srcFile, callback): callback(); });
+ },
+ function(callback){
+ fileExists (destFile, function(exist) { (exist)? fs.unlink(destFile, callback): callback(); });
+ }
+ ], callback);
+ }else{
+ callback();
+ }
}
], function(err) {
-
var status = "ok";
//check for known errors and replace the status
- if(err == "uploadFailed" || err == "convertFailed")
+ if(err == "uploadFailed" || err == "convertFailed" || err == "padHasData")
{
status = err;
err = null;
}
ERR(err);
-
+
//close the connection
- 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);
+ res.send(
+ "<head> \
+ <script type='text/javascript' src='../../static/js/jquery.js'></script> \
+ </head> \
+ <script> \
+ $(window).load(function(){ \
+ if(navigator.userAgent.indexOf('MSIE') === -1){ \
+ document.domain = document.domain; \
+ } \
+ var impexp = window.parent.padimpexp.handleFrameCall('" + directDatabaseAccess +"', '" + status + "'); \
+ }) \
+ </script>"
+ , 200);
});
}
diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js
index e1ac994e..c210ab2b 100644
--- a/src/node/handler/PadMessageHandler.js
+++ b/src/node/handler/PadMessageHandler.js
@@ -37,6 +37,7 @@ var _ = require('underscore');
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
var channels = require("channels");
var stats = require('../stats');
+var remoteAddress = require("../utils/RemoteAddress").remoteAddress;
/**
* A associative array that saves informations about a session
@@ -93,8 +94,18 @@ exports.handleConnect = function(client)
*/
exports.kickSessionsFromPad = function(padID)
{
+ if(typeof socketio.sockets['clients'] !== 'function')
+ return;
+
//skip if there is nobody on this pad
- if(socketio.sockets.clients(padID).length == 0)
+ var roomClients = [], room = socketio.sockets.adapter.rooms[padID];
+ if (room) {
+ for (var id in room) {
+ roomClients.push(socketio.sockets.adapter.nsp.connected[id]);
+ }
+ }
+
+ if(roomClients.length == 0)
return;
//disconnect everyone from this pad
@@ -115,14 +126,16 @@ exports.handleDisconnect = function(client)
//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 IP address from our persistant object
+ var ip = remoteAddress[client.id];
+
+ // 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)
@@ -220,6 +233,8 @@ exports.handleMessage = function(client, message)
} else {
messageLogger.warn("Dropped message, unknown COLLABROOM Data Type " + message.data.type);
}
+ } else if(message.type == "SWITCH_TO_PAD") {
+ handleSwitchToPad(client, message);
} else {
messageLogger.warn("Dropped message, unknown Message Type " + message.type);
}
@@ -233,18 +248,7 @@ exports.handleMessage = function(client, message)
{
// 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
- };
+ createSessionInfo(client, message);
}
// Note: message.sessionID is an entirely different kind of
@@ -253,11 +257,10 @@ exports.handleMessage = function(client, message)
// FIXME: Use a hook instead
// FIXME: Allow to override readwrite access with readonly
- // 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();
+ return;
}else{
var auth = sessioninfos[client.id].auth;
var checkAccessCallback = function(err, statusObject)
@@ -493,14 +496,19 @@ function handleSuggestUserName(client, message)
return;
}
- var padId = sessioninfos[client.id].padId,
- clients = socketio.sockets.clients(padId);
+ var padId = sessioninfos[client.id].padId;
+ var roomClients = [], room = socketio.sockets.adapter.rooms[padId];
+ if (room) {
+ for (var id in room) {
+ roomClients.push(socketio.sockets.adapter.nsp.connected[id]);
+ }
+ }
//search the author and send him this message
- for(var i = 0; i < clients.length; i++) {
- var session = sessioninfos[clients[i].id];
+ for(var i = 0; i < roomClients.length; i++) {
+ var session = sessioninfos[roomClients[i].id];
if(session && session.author == message.data.payload.unnamedId) {
- clients[i].json.send(message);
+ roomClients[i].json.send(message);
break;
}
}
@@ -648,12 +656,17 @@ function handleUserChanges(data, cb)
, op
while(iterator.hasNext()) {
op = iterator.next()
- if(op.opcode != '+') continue;
+
+ //+ can add text with attribs
+ //= can change or add attribs
+ //- can have attribs, but they are discarded and don't show up in the attribs - but do show up in the pool
+
op.attribs.split('*').forEach(function(attr) {
if(!attr) return
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);
+ //the empty author is used in the clearAuthorship functionality so this should be the only exception
+ if('author' == attr[0] && (attr[1] != thisSession.author && attr[1] != '')) throw new Error("Trying to submit changes as another author in changeset "+changeset);
})
}
@@ -694,6 +707,14 @@ function handleUserChanges(data, cb)
// and can be applied after "c".
try
{
+ // a changeset can be based on an old revision with the same changes in it
+ // prevent eplite from accepting it TODO: better send the client a NEW_CHANGES
+ // of that revision
+ if(baseRev+1 == r && c == changeset) {
+ client.json.send({disconnect:"badChangeset"});
+ stats.meter('failedChangesets').mark();
+ return callback(new Error("Won't apply USER_CHANGES, because it contains an already accepted changeset"));
+ }
changeset = Changeset.follow(c, changeset, false, apool);
}catch(e){
client.json.send({disconnect:"badChangeset"});
@@ -724,7 +745,16 @@ function handleUserChanges(data, cb)
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);
+ try
+ {
+ pad.appendRevision(changeset, thisSession.author);
+ }
+ catch(e)
+ {
+ client.json.send({disconnect:"badChangeset"});
+ stats.meter('failedChangesets').mark();
+ return callback(e)
+ }
var correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool);
if (correctionChangeset) {
@@ -753,7 +783,13 @@ function handleUserChanges(data, cb)
exports.updatePadClients = function(pad, callback)
{
//skip this step if noone is on this pad
- var roomClients = socketio.sockets.clients(pad.id);
+ var roomClients = [], room = socketio.sockets.adapter.rooms[pad.id];
+ if (room) {
+ for (var id in room) {
+ roomClients.push(socketio.sockets.adapter.nsp.connected[id]);
+ }
+ }
+
if(roomClients.length==0)
return callback();
@@ -766,10 +802,8 @@ exports.updatePadClients = function(pad, callback)
var revCache = {};
//go trough all sessions on this pad
- async.forEach(roomClients, function(client, callback)
- {
+ async.forEach(roomClients, function(client, callback){
var sid = client.id;
-
//https://github.com/caolan/async#whilst
//send them all new changesets
async.whilst(
@@ -816,10 +850,10 @@ exports.updatePadClients = function(pad, callback)
client.json.send(wireMsg);
}
-
- sessioninfos[sid].time = currentTime;
- sessioninfos[sid].rev = r;
-
+ if(sessioninfos[sid]){
+ sessioninfos[sid].time = currentTime;
+ sessioninfos[sid].rev = r;
+ }
callback(null);
}
], callback);
@@ -875,6 +909,48 @@ function _correctMarkersInPad(atext, apool) {
return builder.toString();
}
+function handleSwitchToPad(client, message)
+{
+ // clear the session and leave the room
+ var currentSession = sessioninfos[client.id];
+ var padId = currentSession.padId;
+ var roomClients = [], room = socketio.sockets.adapter.rooms[padId];
+ if (room) {
+ for (var id in room) {
+ roomClients.push(socketio.sockets.adapter.nsp.connected[id]);
+ }
+ }
+
+ for(var i = 0; i < roomClients.length; i++) {
+ var sinfo = sessioninfos[roomClients[i].id];
+ if(sinfo && sinfo.author == currentSession.author) {
+ // fix user's counter, works on page refresh or if user closes browser window and then rejoins
+ sessioninfos[roomClients[i].id] = {};
+ roomClients[i].leave(padId);
+ }
+ }
+
+ // start up the new pad
+ createSessionInfo(client, message);
+ handleClientReady(client, message);
+}
+
+function createSessionInfo(client, message)
+{
+ // 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
+ };
+}
+
/**
* Handles a CLIENT_READY. A CLIENT_READY is the first message from the client to the server. The Client sends his token
* and the pad it wants to enter. The Server answers with the inital values (clientVars) of the pad
@@ -998,6 +1074,11 @@ function handleClientReady(client, message)
{
authorManager.getAuthor(authorId, function(err, author)
{
+ if(!author && !err)
+ {
+ messageLogger.error("There is no author for authorId:", authorId);
+ return callback();
+ }
if(ERR(err, callback)) return;
historicalAuthorData[authorId] = {name: author.name, colorId: author.colorId}; // Filter author attribs (e.g. don't send author's pads to all clients)
callback();
@@ -1015,7 +1096,13 @@ function handleClientReady(client, message)
return callback();
//Check if this author is already on the pad, if yes, kick the other sessions!
- var roomClients = socketio.sockets.clients(padIds.padId);
+ var roomClients = [], room = socketio.sockets.adapter.rooms[pad.id];
+ if (room) {
+ for (var id in room) {
+ roomClients.push(socketio.sockets.adapter.nsp.connected[id]);
+ }
+ }
+
for(var i = 0; i < roomClients.length; i++) {
var sinfo = sessioninfos[roomClients[i].id];
if(sinfo && sinfo.author == author) {
@@ -1032,19 +1119,19 @@ function handleClientReady(client, message)
sessioninfos[client.id].readonly = padIds.readonly;
//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';
- }
+ var ip = remoteAddress[client.id];
- if(pad.head > 0) {
- accessLogger.info('[ENTER] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" entered the pad');
- }
- else if(pad.head == 0) {
- accessLogger.info('[CREATE] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" created the pad');
- }
- })
+ //Anonymize the IP address if IP logging is disabled
+ if(settings.disableIPlogging) {
+ ip = 'ANONYMOUS';
+ }
+
+ if(pad.head > 0) {
+ accessLogger.info('[ENTER] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" entered the pad');
+ }
+ else if(pad.head == 0) {
+ accessLogger.info('[CREATE] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" created the pad');
+ }
//If this is a reconnect, we don't have to send the client the ClientVars again
if(message.reconnect == true)
@@ -1165,7 +1252,14 @@ function handleClientReady(client, message)
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)
+ var roomClients = [], room = socketio.sockets.adapter.rooms[pad.id];
+ if (room) {
+ for (var id in room) {
+ roomClients.push(socketio.sockets.adapter.nsp.connected[id]);
+ }
+ }
+
+ async.forEach(roomClients, function(roomClient, callback)
{
var author;
@@ -1540,10 +1634,15 @@ function composePadChangesets(padId, startNum, endNum, callback)
changeset = changesets[startNum];
var pool = pad.apool();
- for(var r=startNum+1;r<endNum;r++)
- {
- var cs = changesets[r];
- changeset = Changeset.compose(changeset, cs, pool);
+ try {
+ for(var r=startNum+1;r<endNum;r++) {
+ var cs = changesets[r];
+ changeset = Changeset.compose(changeset, cs, pool);
+ }
+ } catch(e){
+ // r-1 indicates the rev that was build starting with startNum, applying startNum+1, +2, +3
+ console.warn("failed to compose cs in pad:",padId," startrev:",startNum," current rev:",r);
+ return callback(e);
}
callback(null);
@@ -1561,8 +1660,16 @@ function composePadChangesets(padId, startNum, endNum, callback)
* Get the number of users in a pad
*/
exports.padUsersCount = function (padID, callback) {
+
+ var roomClients = [], room = socketio.sockets.adapter.rooms[padID];
+ if (room) {
+ for (var id in room) {
+ roomClients.push(socketio.sockets.adapter.nsp.connected[id]);
+ }
+ }
+
callback(null, {
- padUsersCount: socketio.sockets.clients(padID).length
+ padUsersCount: roomClients.length
});
}
@@ -1572,7 +1679,14 @@ exports.padUsersCount = function (padID, callback) {
exports.padUsers = function (padID, callback) {
var result = [];
- async.forEach(socketio.sockets.clients(padID), function(roomClient, callback) {
+ var roomClients = [], room = socketio.sockets.adapter.rooms[padID];
+ if (room) {
+ for (var id in room) {
+ roomClients.push(socketio.sockets.adapter.nsp.connected[id]);
+ }
+ }
+
+ async.forEach(roomClients, function(roomClient, callback) {
var s = sessioninfos[roomClient.id];
if(s) {
authorManager.getAuthor(s.author, function(err, author) {
diff --git a/src/node/handler/SocketIORouter.js b/src/node/handler/SocketIORouter.js
index b3e046d2..0a7361f4 100644
--- a/src/node/handler/SocketIORouter.js
+++ b/src/node/handler/SocketIORouter.js
@@ -24,6 +24,7 @@ var log4js = require('log4js');
var messageLogger = log4js.getLogger("message");
var securityManager = require("../db/SecurityManager");
var readOnlyManager = require("../db/ReadOnlyManager");
+var remoteAddress = require("../utils/RemoteAddress").remoteAddress;
var settings = require('../utils/Settings');
/**
@@ -56,11 +57,15 @@ exports.setSocketIO = function(_socket) {
socket.sockets.on('connection', function(client)
{
+
+ // Broken: See http://stackoverflow.com/questions/4647348/send-message-to-specific-client-with-socket-io-and-node-js
+ // Fixed by having a persistant object, ideally this would actually be in the database layer
+ // TODO move to database layer
if(settings.trustProxy && client.handshake.headers['x-forwarded-for'] !== undefined){
- client.set('remoteAddress', client.handshake.headers['x-forwarded-for']);
+ remoteAddress[client.id] = client.handshake.headers['x-forwarded-for'];
}
else{
- client.set('remoteAddress', client.handshake.address.address);
+ remoteAddress[client.id] = client.handshake.address;
}
var clientAuthorized = false;
diff --git a/src/node/hooks/express.js b/src/node/hooks/express.js
index c6573c80..bf849419 100644
--- a/src/node/hooks/express.js
+++ b/src/node/hooks/express.js
@@ -10,24 +10,11 @@ var server;
var serverName;
exports.createServer = function () {
- //try to get the git version
- var version = "";
- try
- {
- var rootPath = path.resolve(npm.dir, '..');
- var ref = fs.readFileSync(rootPath + "/.git/HEAD", "utf-8");
- 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 git version is " + version);
- }
- catch(e)
- {
- console.warn("Can't get git version for server header\n" + e.message)
- }
console.log("Report bugs at https://github.com/ether/etherpad-lite/issues")
- serverName = "Etherpad " + version + " (http://etherpad.org)";
+ serverName = "Etherpad " + settings.getGitCommit() + " (http://etherpad.org)";
+
+ console.log("Your Etherpad version is " + settings.getEpVersion() + " (" + settings.getGitCommit() + ")");
exports.restartServer();
@@ -38,7 +25,6 @@ exports.createServer = function () {
else{
console.warn("Admin username and password not set in settings.json. To access admin please uncomment and edit 'users' in settings.json");
}
-
}
exports.restartServer = function () {
@@ -56,7 +42,7 @@ exports.restartServer = function () {
console.log( "SSL -- server key file: " + settings.ssl.key );
console.log( "SSL -- Certificate Authority's certificate file: " + settings.ssl.cert );
- options = {
+ var options = {
key: fs.readFileSync( settings.ssl.key ),
cert: fs.readFileSync( settings.ssl.cert )
};
diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js
index d8f19bba..1ae8d7b5 100644
--- a/src/node/hooks/express/adminplugins.js
+++ b/src/node/hooks/express/adminplugins.js
@@ -1,10 +1,9 @@
-var path = require('path');
var eejs = require('ep_etherpad-lite/node/eejs');
+var settings = require('ep_etherpad-lite/node/utils/Settings');
var installer = require('ep_etherpad-lite/static/js/pluginfw/installer');
var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
var _ = require('underscore');
var semver = require('semver');
-var async = require('async');
exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/admin/plugins', function(req, res) {
@@ -14,18 +13,25 @@ exports.expressCreateServer = function (hook_name, args, cb) {
search_results: {},
errors: [],
};
-
res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins.html", render_args) );
});
args.app.get('/admin/plugins/info', function(req, res) {
- res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html", {}) );
+ var gitCommit = settings.getGitCommit();
+ var epVersion = settings.getEpVersion();
+ res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html",
+ {
+ gitCommit: gitCommit,
+ epVersion: epVersion
+ })
+ );
});
}
exports.socketio = function (hook_name, args, cb) {
var io = args.io.of("/pluginfw/installer");
io.on('connection', function (socket) {
- if (!socket.handshake.session.user || !socket.handshake.session.user.is_admin) return;
+
+ if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return;
socket.on("getInstalled", function (query) {
// send currently installed plugins
@@ -85,7 +91,7 @@ exports.socketio = function (hook_name, args, cb) {
socket.on("install", function (plugin_name) {
installer.install(plugin_name, function (er) {
if(er) console.warn(er)
- socket.emit("finished:install", {plugin: plugin_name, error: er? er.message : null});
+ socket.emit("finished:install", {plugin: plugin_name, code: er? er.code : null, error: er? er.message : null});
});
});
@@ -107,4 +113,4 @@ function sortPluginList(plugins, property, /*ASC?*/dir) {
// a must be equal to b
return 0;
})
-} \ No newline at end of file
+}
diff --git a/src/node/hooks/express/adminsettings.js b/src/node/hooks/express/adminsettings.js
index 2a48d289..4986f093 100644
--- a/src/node/hooks/express/adminsettings.js
+++ b/src/node/hooks/express/adminsettings.js
@@ -1,7 +1,5 @@
-var path = require('path');
var eejs = require('ep_etherpad-lite/node/eejs');
var settings = require('ep_etherpad-lite/node/utils/Settings');
-var installer = require('ep_etherpad-lite/static/js/pluginfw/installer');
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var fs = require('fs');
@@ -22,7 +20,8 @@ exports.expressCreateServer = function (hook_name, args, cb) {
exports.socketio = function (hook_name, args, cb) {
var io = args.io.of("/settings");
io.on('connection', function (socket) {
- if (!socket.handshake.session.user || !socket.handshake.session.user.is_admin) return;
+
+ if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return;
socket.on("load", function (query) {
fs.readFile('settings.json', 'utf8', function (err,data) {
diff --git a/src/node/hooks/express/importexport.js b/src/node/hooks/express/importexport.js
index f5a3e5a1..f3f05163 100644
--- a/src/node/hooks/express/importexport.js
+++ b/src/node/hooks/express/importexport.js
@@ -5,7 +5,7 @@ var importHandler = require('../../handler/ImportHandler');
exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/p/:pad/:rev?/export/:type', function(req, res, next) {
- var types = ["pdf", "doc", "txt", "html", "odt", "dokuwiki"];
+ var types = ["pdf", "doc", "txt", "html", "odt", "etherpad"];
//send a 404 if we don't support this filetype
if (types.indexOf(req.params.type) == -1) {
next();
diff --git a/src/node/hooks/express/padreadonly.js b/src/node/hooks/express/padreadonly.js
index 9a0a52bf..d60d3863 100644
--- a/src/node/hooks/express/padreadonly.js
+++ b/src/node/hooks/express/padreadonly.js
@@ -10,7 +10,6 @@ exports.expressCreateServer = function (hook_name, args, cb) {
{
var html;
var padId;
- var pad;
async.series([
//translate the read only pad to a padId
diff --git a/src/node/hooks/express/socketio.js b/src/node/hooks/express/socketio.js
index 524bab3d..35d6d074 100644
--- a/src/node/hooks/express/socketio.js
+++ b/src/node/hooks/express/socketio.js
@@ -1,6 +1,5 @@
-var log4js = require('log4js');
-var socketio = require('socket.io');
var settings = require('../../utils/Settings');
+var socketio = require('socket.io');
var socketIORouter = require("../../handler/SocketIORouter");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var webaccess = require("ep_etherpad-lite/node/hooks/express/webaccess");
@@ -11,14 +10,25 @@ var connect = require('connect');
exports.expressCreateServer = function (hook_name, args, cb) {
//init socket.io and redirect all requests to the MessageHandler
- var io = socketio.listen(args.server);
+ // there shouldn't be a browser that isn't compatible to all
+ // transports in this list at once
+ // e.g. XHR is disabled in IE by default, so in IE it should use jsonp-polling
+ var io = socketio({
+ transports: settings.socketTransportProtocols
+ }).listen(args.server);
/* Require an express session cookie to be present, and load the
* session. See http://www.danielbaulig.de/socket-ioexpress for more
* info */
- io.set('authorization', function (data, accept) {
- if (!data.headers.cookie) return accept('No session cookie transmitted.', false);
+ io.use(function(socket, accept) {
+ var data = socket.request;
+ // Use a setting if we want to allow load Testing
+ if(!data.headers.cookie && settings.loadTest){
+ accept(null, true);
+ }else{
+ if (!data.headers.cookie) return accept('No session cookie transmitted.', false);
+ }
// Use connect's cookie parser, because it knows how to parse signed cookies
connect.cookieParser(webaccess.secret)(data, {}, function(err){
if(err) {
@@ -36,35 +46,17 @@ exports.expressCreateServer = function (hook_name, args, cb) {
});
});
- // there shouldn't be a browser that isn't compatible to all
- // transports in this list at once
- // e.g. XHR is disabled in IE by default, so in IE it should use jsonp-polling
- io.set('transports', settings.socketTransportProtocols );
-
- var socketIOLogger = log4js.getLogger("socket.io");
- io.set('logger', {
- debug: function (str)
- {
- socketIOLogger.debug.apply(socketIOLogger, arguments);
- },
- info: function (str)
- {
- socketIOLogger.info.apply(socketIOLogger, arguments);
- },
- warn: function (str)
- {
- socketIOLogger.warn.apply(socketIOLogger, arguments);
- },
- error: function (str)
- {
- socketIOLogger.error.apply(socketIOLogger, arguments);
- },
- });
+ // var socketIOLogger = log4js.getLogger("socket.io");
+ // Debug logging now has to be set at an environment level, this is stupid.
+ // https://github.com/Automattic/socket.io/wiki/Migrating-to-1.0
+ // This debug logging environment is set in Settings.js
//minify socket.io javascript
- if(settings.minify)
- io.enable('browser client minification');
-
+ // Due to a shitty decision by the SocketIO team minification is
+ // no longer available, details available at:
+ // http://stackoverflow.com/questions/23981741/minify-socket-io-socket-io-js-with-1-0
+ // if(settings.minify) io.enable('browser client minification');
+
//Initalize the Socket.IO Router
socketIORouter.setSocketIO(io);
socketIORouter.addComponent("pad", padMessageHandler);
diff --git a/src/node/hooks/express/static.js b/src/node/hooks/express/static.js
index 7d654c1b..e5a2bff0 100644
--- a/src/node/hooks/express/static.js
+++ b/src/node/hooks/express/static.js
@@ -1,11 +1,8 @@
-var path = require('path');
var minify = require('../../utils/Minify');
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
var CachingMiddleware = require('../../utils/caching_middleware');
var settings = require("../../utils/Settings");
-var Yajsml = require('yajsml');
-var fs = require("fs");
-var ERR = require("async-stacktrace");
+var Yajsml = require('etherpad-yajsml');
var _ = require("underscore");
exports.expressCreateServer = function (hook_name, args, cb) {
diff --git a/src/node/hooks/express/swagger.js b/src/node/hooks/express/swagger.js
index e8daa61c..f606eb88 100644
--- a/src/node/hooks/express/swagger.js
+++ b/src/node/hooks/express/swagger.js
@@ -1,4 +1,3 @@
-var log4js = require('log4js');
var express = require('express');
var apiHandler = require('../../handler/APIHandler');
var apiCaller = require('./apicalls').apiCaller;
@@ -285,6 +284,10 @@ var API = {
}
},
"response": {"chatHead":{"type":"Message"}}
+ },
+ "appendChatMessage": {
+ "func": "appendChatMessage",
+ "description": "appends a chat message"
}
}
};
@@ -356,7 +359,17 @@ exports.expressCreateServer = function (hook_name, args, cb) {
args.app.use(basePath, subpath);
- swagger.setAppHandler(subpath);
+ //hack!
+ var swagger_temp = swagger
+ swagger = swagger.createNew(subpath);
+ swagger.params = swagger_temp.params
+ swagger.queryParam = swagger_temp.queryParam
+ swagger.pathParam = swagger_temp.pathParam
+ swagger.bodyParam = swagger_temp.bodyParam
+ swagger.formParam = swagger_temp.formParam
+ swagger.headerParam = swagger_temp.headerParam
+ swagger.error = swagger_temp.error
+ //swagger.setAppHandler(subpath);
swagger.addModels(swaggerModels);
diff --git a/src/node/hooks/express/tests.js b/src/node/hooks/express/tests.js
index 3157d68e..151c99fa 100644
--- a/src/node/hooks/express/tests.js
+++ b/src/node/hooks/express/tests.js
@@ -23,6 +23,10 @@ exports.expressCreateServer = function (hook_name, args, cb) {
});
+
+ // path.join seems to normalize by default, but we'll just be explicit
+ var rootTestFolder = path.normalize(path.join(npm.root, "../tests/frontend/"));
+
var url2FilePath = function(url){
var subPath = url.substr("/tests/frontend".length);
if (subPath == ""){
@@ -30,8 +34,11 @@ exports.expressCreateServer = function (hook_name, args, cb) {
}
subPath = subPath.split("?")[0];
- var filePath = path.normalize(npm.root + "/../tests/frontend/")
- filePath += subPath.replace("..", "");
+ var filePath = path.normalize(path.join(rootTestFolder, subPath));
+ // make sure we jail the paths to the test folder, otherwise serve index
+ if (filePath.indexOf(rootTestFolder) !== 0) {
+ filePath = path.join(rootTestFolder, "index.html");
+ }
return filePath;
}
diff --git a/src/node/hooks/express/webaccess.js b/src/node/hooks/express/webaccess.js
index 6998853f..b798f2c7 100644
--- a/src/node/hooks/express/webaccess.js
+++ b/src/node/hooks/express/webaccess.js
@@ -2,7 +2,6 @@ var express = require('express');
var log4js = require('log4js');
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')
diff --git a/src/node/hooks/i18n.js b/src/node/hooks/i18n.js
index 62631b93..67815659 100644
--- a/src/node/hooks/i18n.js
+++ b/src/node/hooks/i18n.js
@@ -1,7 +1,6 @@
var languages = require('languages4translatewiki')
, fs = require('fs')
, path = require('path')
- , express = require('express')
, _ = require('underscore')
, npm = require('npm')
, plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins.js').plugins
diff --git a/src/node/utils/Abiword.js b/src/node/utils/Abiword.js
index 5f12bd97..1d9ac5d3 100644
--- a/src/node/utils/Abiword.js
+++ b/src/node/utils/Abiword.js
@@ -18,7 +18,6 @@
* limitations under the License.
*/
-var util = require('util');
var spawn = require('child_process').spawn;
var async = require("async");
var settings = require("./Settings");
diff --git a/src/node/utils/ExportDokuWiki.js b/src/node/utils/ExportDokuWiki.js
deleted file mode 100644
index f5d2d177..00000000
--- a/src/node/utils/ExportDokuWiki.js
+++ /dev/null
@@ -1,350 +0,0 @@
-/**
- * Copyright 2011 Adrian Lang
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * 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");
-
-function getPadDokuWiki(pad, revNum, callback)
-{
- var atext = pad.atext;
- var dokuwiki;
- async.waterfall([
- // fetch revision atext
-
-
- function (callback)
- {
- if (revNum != undefined)
- {
- pad.getInternalRevisionAText(revNum, function (err, revisionAtext)
- {
- atext = revisionAtext;
- callback(err);
- });
- }
- else
- {
- callback(null);
- }
- },
-
- // convert atext to dokuwiki text
-
- function (callback)
- {
- dokuwiki = getDokuWikiFromAtext(pad, atext);
- callback(null);
- }],
- // run final callback
-
-
- function (err)
- {
- callback(err, dokuwiki);
- });
-}
-
-function getDokuWikiFromAtext(pad, atext)
-{
- var apool = pad.apool();
- var textLines = atext.text.slice(0, -1).split('\n');
- var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
-
- var tags = ['======', '=====', '**', '//', '__', 'del>'];
- var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
- var anumMap = {};
-
- props.forEach(function (propName, i)
- {
- var propTrueNum = apool.putAttrib([propName, true], true);
- if (propTrueNum >= 0)
- {
- anumMap[propTrueNum] = i;
- }
- });
-
- function getLineDokuWiki(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();
-
- function emitOpenTag(i)
- {
- if (tags[i].indexOf('>') !== -1) {
- assem.append('<');
- }
- assem.append(tags[i]);
- }
-
- function emitCloseTag(i)
- {
- if (tags[i].indexOf('>') !== -1) {
- assem.append('</');
- }
- assem.append(tags[i]);
- }
-
- var urls = _findURLs(text);
-
- 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
- }
- }
- }
-
- for (var i = propVals.length - 1; i >= 0; i--)
- {
- if (propVals[i] === LEAVE)
- {
- emitCloseTag(i);
- propVals[i] = false;
- }
- else if (propVals[i] === STAY)
- {
- emitCloseTag(i);
- }
- }
- for (var i = 0; i < propVals.length; i++)
- {
- if (propVals[i] === ENTER || propVals[i] === STAY)
- {
- emitOpenTag(i);
- 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);
-
- assem.append(_escapeDokuWiki(s));
- } // end iteration over spans in line
- for (var i = propVals.length - 1; i >= 0; i--)
- {
- if (propVals[i])
- {
- emitCloseTag(i);
- propVals[i] = false;
- }
- }
- } // end processNextChars
- if (urls)
- {
- urls.forEach(function (urlData)
- {
- var startIndex = urlData[0];
- var url = urlData[1];
- var urlLength = url.length;
- processNextChars(startIndex - idx);
- assem.append('[[');
-
- // Do not use processNextChars since a link does not contain syntax and
- // needs no escaping
- var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + urlLength));
- idx += urlLength;
- assem.append(taker.take(iter.next().chars));
-
- assem.append(']]');
- });
- }
- processNextChars(text.length - idx);
-
- return assem.toString() + "\n";
- } // end getLineDokuWiki
- var pieces = [];
-
- for (var i = 0; i < textLines.length; i++)
- {
- var line = _analyzeLine(textLines[i], attribLines[i], apool);
- var lineContent = getLineDokuWiki(line.text, line.aline);
-
- if (line.listLevel && lineContent)
- {
- if (line.listTypeName == "number")
- {
- pieces.push(new Array(line.listLevel + 1).join(' ') + ' - ');
- } else {
- pieces.push(new Array(line.listLevel + 1).join(' ') + '* ');
- }
- }
- pieces.push(lineContent);
- }
-
- 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.getPadDokuWikiDocument = function (padId, revNum, callback)
-{
- padManager.getPad(padId, function (err, pad)
- {
- if (err)
- {
- callback(err);
- return;
- }
-
- getPadDokuWiki(pad, revNum, callback);
- });
-};
-
-function _escapeDokuWiki(s)
-{
- s = s.replace(/(\/\/|\*\*|__)/g, '%%$1%%');
- return s;
-}
-
-// copied from ACE
-var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
-var _REGEX_SPACE = /\s/;
-var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')');
-var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g');
-
-// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]
-
-
-function _findURLs(text)
-{
- _REGEX_URL.lastIndex = 0;
- var urls = null;
- var execResult;
- while ((execResult = _REGEX_URL.exec(text)))
- {
- urls = (urls || []);
- var startIndex = execResult.index;
- var url = execResult[0];
- urls.push([startIndex, url]);
- }
-
- return urls;
-}
diff --git a/src/node/utils/ExportEtherpad.js b/src/node/utils/ExportEtherpad.js
new file mode 100644
index 00000000..46ae0d7a
--- /dev/null
+++ b/src/node/utils/ExportEtherpad.js
@@ -0,0 +1,79 @@
+/**
+ * 2014 John McLear (Etherpad Foundation / McLear 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 db = require("../db/DB").db;
+var ERR = require("async-stacktrace");
+
+exports.getPadRaw = function(padId, callback){
+ async.waterfall([
+ function(cb){
+
+ // Get the Pad
+ db.findKeys("pad:"+padId, null, function(err,padcontent){
+ if(!err){
+ cb(err, padcontent);
+ }
+ })
+ },
+ function(padcontent,cb){
+
+ // Get the Pad available content keys
+ db.findKeys("pad:"+padId+":*", null, function(err,records){
+ if(!err){
+ for (var key in padcontent) { records.push(padcontent[key]);}
+ cb(err, records);
+ }
+ })
+ },
+ function(records, cb){
+ var data = {};
+
+ async.forEachSeries(Object.keys(records), function(key, r){
+
+ // For each piece of info about a pad.
+ db.get(records[key], function(err, entry){
+ data[records[key]] = entry;
+
+ // Get the Pad Authors
+ if(entry.pool && entry.pool.numToAttrib){
+ var authors = entry.pool.numToAttrib;
+ async.forEachSeries(Object.keys(authors), function(k, c){
+ if(authors[k][0] === "author"){
+ var authorId = authors[k][1];
+
+ // Get the author info
+ db.get("globalAuthor:"+authorId, function(e, authorEntry){
+ if(authorEntry && authorEntry.padIDs) authorEntry.padIDs = padId;
+ if(!e) data["globalAuthor:"+authorId] = authorEntry;
+ });
+
+ }
+ // console.log("authorsK", authors[k]);
+ c(null);
+ });
+ }
+ r(null); // callback;
+ });
+ }, function(err){
+ cb(err, data);
+ })
+ }
+ ], function(err, data){
+ callback(null, data);
+ });
+}
diff --git a/src/node/utils/ExportHelper.js b/src/node/utils/ExportHelper.js
index 136896f0..297c2d7a 100644
--- a/src/node/utils/ExportHelper.js
+++ b/src/node/utils/ExportHelper.js
@@ -18,12 +18,7 @@
* 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());
@@ -60,7 +55,7 @@ exports._analyzeLine = function(text, aline, apool){
var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool);
if (listType){
lineMarker = 1;
- listType = /([a-z]+)([12345678])/.exec(listType);
+ listType = /([a-z]+)([0-9]+)/.exec(listType);
if (listType){
line.listTypeName = listType[1];
line.listLevel = Number(listType[2]);
diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js
index 01920da7..9e1ba124 100644
--- a/src/node/utils/ExportHtml.js
+++ b/src/node/utils/ExportHtml.js
@@ -30,8 +30,6 @@ function getPadHTML(pad, revNum, callback)
var html;
async.waterfall([
// fetch revision atext
-
-
function (callback)
{
if (revNum != undefined)
@@ -78,6 +76,14 @@ function getHTMLFromAtext(pad, atext, authorColors)
var tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
+
+ hooks.aCallAll("exportHtmlAdditionalTags", pad, function(err, newProps){
+ newProps.forEach(function (propName, i){
+ tags.push(propName);
+ props.push(propName);
+ });
+ });
+
// holds a map of used styling attributes (*1, *2, etc) in the apool
// and maps them to an index in props
// *3:2 -> the attribute *3 means strong
@@ -297,10 +303,12 @@ function getHTMLFromAtext(pad, atext, authorColors)
// want to deal gracefully with blank lines.
// => keeps track of the parents level of indentation
var lists = []; // e.g. [[1,'bullet'], [3,'bullet'], ...]
+ var listLevels = []
for (var i = 0; i < textLines.length; i++)
{
var line = _analyzeLine(textLines[i], attribLines[i], apool);
var lineContent = getLineHTML(line.text, line.aline);
+ listLevels.push(line.listLevel)
if (line.listLevel)//If we are inside a list
{
@@ -320,13 +328,27 @@ function getHTMLFromAtext(pad, atext, authorColors)
if (whichList >= lists.length)//means we are on a deeper level of indentation than the previous line
{
+ if(lists.length > 0){
+ pieces.push('</li>')
+ }
lists.push([line.listLevel, line.listTypeName]);
+
+ // if there is a previous list we need to open x tags, where x is the difference of the levels
+ // if there is no previous list we need to open x tags, where x is the wanted level
+ var toOpen = lists.length > 1 ? line.listLevel - lists[lists.length - 2][0] - 1 : line.listLevel - 1
+
if(line.listTypeName == "number")
{
+ if(toOpen > 0){
+ pieces.push(new Array(toOpen + 1).join('<ol>'))
+ }
pieces.push('<ol class="'+line.listTypeName+'"><li>', lineContent || '<br>');
}
else
{
+ if(toOpen > 0){
+ pieces.push(new Array(toOpen + 1).join('<ul>'))
+ }
pieces.push('<ul class="'+line.listTypeName+'"><li>', lineContent || '<br>');
}
}
@@ -355,44 +377,50 @@ function getHTMLFromAtext(pad, atext, authorColors)
pieces.push('<br><br>');
}
}*/
- else//means we are getting closer to the lowest level of indentation
+ else//means we are getting closer to the lowest level of indentation or are at the same level
{
- while (whichList < lists.length - 1)
- {
+ var toClose = lists.length > 0 ? listLevels[listLevels.length - 2] - line.listLevel : 0
+ if( toClose > 0){
+ pieces.push('</li>')
if(lists[lists.length - 1][1] == "number")
{
- pieces.push('</li></ol>');
+ pieces.push(new Array(toClose+1).join('</ol>'))
+ pieces.push('<li>', lineContent || '<br>');
}
else
{
- pieces.push('</li></ul>');
+ pieces.push(new Array(toClose+1).join('</ul>'))
+ pieces.push('<li>', lineContent || '<br>');
}
- lists.length--;
+ lists = lists.slice(0,whichList+1)
+ } else {
+ pieces.push('</li><li>', lineContent || '<br>');
}
- pieces.push('</li><li>', lineContent || '<br>');
}
}
- else//outside any list
+ else//outside any list, need to close line.listLevel of lists
{
- while (lists.length > 0)//if was in a list: close it before
- {
- if(lists[lists.length - 1][1] == "number")
- {
+ if(lists.length > 0){
+ if(lists[lists.length - 1][1] == "number"){
pieces.push('</li></ol>');
- }
- else
- {
+ pieces.push(new Array(listLevels[listLevels.length - 2]).join('</ol>'))
+ } else {
pieces.push('</li></ul>');
+ pieces.push(new Array(listLevels[listLevels.length - 2]).join('</ul>'))
}
- lists.length--;
}
- var lineContentFromHook = hooks.callAllStr("getLineHTMLForExport",
- {
+ lists = []
+
+ var context = {
line: line,
+ lineContent: lineContent,
apool: apool,
attribLine: attribLines[i],
text: textLines[i]
- }, " ", " ", "");
+ }
+
+ var lineContentFromHook = hooks.callAllStr("getLineHTMLForExport", context, " ", " ", "");
+
if (lineContentFromHook)
{
pieces.push(lineContentFromHook, '');
@@ -425,37 +453,120 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
{
if(ERR(err, callback)) return;
- var head =
- (noDocType ? '' : '<!doctype html>\n') +
- '<html lang="en">\n' + (noDocType ? '' : '<head>\n' +
- '<title>' + Security.escapeHTML(padId) + '</title>\n' +
- '<meta charset="utf-8">\n' +
- '<style> * { font-family: arial, sans-serif;\n' +
- 'font-size: 13px;\n' +
- 'line-height: 17px; }' +
- 'ul.indent { list-style-type: none; }' +
- 'ol { list-style-type: decimal; }' +
- 'ol ol { list-style-type: lower-latin; }' +
- 'ol ol ol { list-style-type: lower-roman; }' +
- 'ol ol ol ol { list-style-type: decimal; }' +
- 'ol ol ol ol ol { list-style-type: lower-latin; }' +
- 'ol ol ol ol ol ol{ list-style-type: lower-roman; }' +
- 'ol ol ol ol ol ol ol { list-style-type: decimal; }' +
- 'ol ol ol ol ol ol ol ol{ list-style-type: lower-latin; }' +
- '</style>\n' + '</head>\n') +
- '<body>';
-
- var foot = '</body>\n</html>\n';
-
- getPadHTML(pad, revNum, function (err, html)
- {
- if(ERR(err, callback)) return;
- callback(null, head + html + foot);
+ var stylesForExportCSS = "";
+ // Include some Styles into the Head for Export
+ hooks.aCallAll("stylesForExport", padId, function(err, stylesForExport){
+ stylesForExport.forEach(function(css){
+ stylesForExportCSS += css;
+ });
+ // Core inclusion of head etc.
+ var head =
+ (noDocType ? '' : '<!doctype html>\n') +
+ '<html lang="en">\n' + (noDocType ? '' : '<head>\n' +
+ '<title>' + Security.escapeHTML(padId) + '</title>\n' +
+ '<meta charset="utf-8">\n' +
+ '<style> * { font-family: arial, sans-serif;\n' +
+ 'font-size: 13px;\n' +
+ 'line-height: 17px; }' +
+ 'ul.indent { list-style-type: none; }' +
+
+ 'ol { list-style-type: none; padding-left:0;}' +
+ 'body > ol { counter-reset: first second third fourth fifth sixth seventh eigth ninth tenth eleventh twelth thirteenth fourteenth fifteenth sixteenth; }' +
+ 'ol > li:before {' +
+ 'content: counter(first) ". " ;'+
+ 'counter-increment: first;}' +
+
+ 'ol > ol > li:before {' +
+ 'content: counter(first) "." counter(second) ". " ;'+
+ 'counter-increment: second;}' +
+
+ 'ol > ol > ol > li:before {' +
+ 'content: counter(first) "." counter(second) "." counter(third) ". ";'+
+ 'counter-increment: third;}' +
+
+ 'ol > ol > ol > ol > li:before {' +
+ 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) ". ";'+
+ 'counter-increment: fourth;}' +
+
+ 'ol > ol > ol > ol > ol > li:before {' +
+ 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) ". ";'+
+ 'counter-increment: fifth;}' +
+
+ 'ol > ol > ol > ol > ol > ol > li:before {' +
+ 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) ". ";'+
+ 'counter-increment: sixth;}' +
+
+ 'ol > ol > ol > ol > ol > ol > ol > li:before {' +
+ 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) ". ";'+
+ 'counter-increment: seventh;}' +
+
+ 'ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
+ 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) ". ";'+
+ 'counter-increment: eigth;}' +
+
+ 'ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
+ 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) ". ";'+
+ 'counter-increment: ninth;}' +
+
+ 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
+ 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) ". ";'+
+ 'counter-increment: tenth;}' +
+
+ 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
+ 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) ". ";'+
+ 'counter-increment: eleventh;}' +
+
+ 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
+ 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) ". ";'+
+ 'counter-increment: twelth;}' +
+
+ 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
+ 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) ". ";'+
+ 'counter-increment: thirteenth;}' +
+
+ 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
+ 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) "." counter(fourteenth) ". ";'+
+ 'counter-increment: fourteenth;}' +
+
+ 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
+ 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) "." counter(fourteenth) "." counter(fifteenth) ". ";'+
+ 'counter-increment: fifteenth;}' +
+
+ 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' +
+ 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) "." counter(fourteenth) "." counter(fifteenth) "." counter(sixthteenth) ". ";'+
+ 'counter-increment: sixthteenth;}' +
+
+ 'ol{ text-indent: 0px; }' +
+ 'ol > ol{ text-indent: 10px; }' +
+ 'ol > ol > ol{ text-indent: 20px; }' +
+ 'ol > ol > ol > ol{ text-indent: 30px; }' +
+ 'ol > ol > ol > ol > ol{ text-indent: 40px; }' +
+ 'ol > ol > ol > ol > ol > ol{ text-indent: 50px; }' +
+ 'ol > ol > ol > ol > ol > ol > ol{ text-indent: 60px; }' +
+ 'ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 70px; }' +
+ 'ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 80px; }' +
+ 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 90px; }' +
+ 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 100px; }' +
+ 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 110px; }' +
+ 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol { text-indent: 120px; }' +
+ 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 130px; }' +
+ 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 140px; }' +
+ 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 150px; }' +
+
+ stylesForExportCSS +
+ '</style>\n' + '</head>\n') +
+ '<body>';
+ var foot = '</body>\n</html>\n';
+
+ getPadHTML(pad, revNum, function (err, html)
+ {
+ if(ERR(err, callback)) return;
+ callback(null, head + html + foot);
+ });
});
});
};
-
// copied from ACE
var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
var _REGEX_SPACE = /\s/;
diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js
index f0b62743..a6bec4a5 100644
--- a/src/node/utils/ExportTxt.js
+++ b/src/node/utils/ExportTxt.js
@@ -22,9 +22,6 @@ 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
@@ -82,7 +79,6 @@ function getTXTFromAtext(pad, atext, authorColors)
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 = "";
@@ -110,7 +106,6 @@ function getTXTFromAtext(pad, atext, authorColors)
// <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;
@@ -250,7 +245,6 @@ function getTXTFromAtext(pad, atext, authorColors)
// so we want to do something reasonable there. We also
// want to deal gracefully with blank lines.
// => keeps track of the parents level of indentation
- var lists = []; // e.g. [[1,'bullet'], [3,'bullet'], ...]
for (var i = 0; i < textLines.length; i++)
{
var line = _analyzeLine(textLines[i], attribLines[i], apool);
diff --git a/src/node/utils/ImportEtherpad.js b/src/node/utils/ImportEtherpad.js
new file mode 100644
index 00000000..37863bff
--- /dev/null
+++ b/src/node/utils/ImportEtherpad.js
@@ -0,0 +1,83 @@
+/**
+ * 2014 John McLear (Etherpad Foundation / McLear 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 log4js = require('log4js');
+var async = require("async");
+var db = require("../db/DB").db;
+
+exports.setPadRaw = function(padId, records, callback){
+ records = JSON.parse(records);
+
+ // !! HACK !!
+ // If you have a really large pad it will cause a Maximum Range Stack crash
+ // This is a temporary patch for that so things are kept stable.
+ var recordCount = Object.keys(records).length;
+ if(recordCount >= 50000){
+ console.warn("Etherpad file is too large to import.. We need to fix this. See https://github.com/ether/etherpad-lite/issues/2524");
+ return callback("tooLarge", false);
+ }
+
+ async.eachSeries(Object.keys(records), function(key, cb){
+ var value = records[key]
+
+ if(!value){
+ cb(); // null values are bad.
+ }
+
+ // Author data
+ if(value.padIDs){
+ // rewrite author pad ids
+ value.padIDs[padId] = 1;
+ var newKey = key;
+
+ // Does this author already exist?
+ db.get(key, function(err, author){
+ if(author){
+ // Yes, add the padID to the author..
+ if( Object.prototype.toString.call(author) === '[object Array]'){
+ author.padIDs.push(padId);
+ }
+ value = author;
+ }else{
+ // No, create a new array with the author info in
+ value.padIDs = [padId];
+ }
+ });
+
+ // Not author data, probably pad data
+ }else{
+ // we can split it to look to see if its pad data
+ var oldPadId = key.split(":");
+
+ // we know its pad data..
+ if(oldPadId[0] === "pad"){
+
+ // so set the new pad id for the author
+ oldPadId[1] = padId;
+
+ // and create the value
+ var newKey = oldPadId.join(":"); // create the new key
+ }
+
+ }
+ // Write the value to the server
+ db.set(newKey, value);
+
+ cb();
+ }, function(){
+ callback(null, true);
+ });
+}
diff --git a/src/node/utils/ImportHtml.js b/src/node/utils/ImportHtml.js
index 48188dfd..33fd91c6 100644
--- a/src/node/utils/ImportHtml.js
+++ b/src/node/utils/ImportHtml.js
@@ -14,23 +14,22 @@
* limitations under the License.
*/
-var jsdom = require('jsdom-nocontextifiy').jsdom;
var log4js = require('log4js');
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var contentcollector = require("ep_etherpad-lite/static/js/contentcollector");
+var cheerio = require("cheerio");
function setPadHTML(pad, html, callback)
{
var apiLogger = log4js.getLogger("ImportHtml");
- // Parse the incoming HTML with jsdom
- 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>");
- }
+ var $ = cheerio.load(html);
+
+ // Appends a line break, used by Etherpad to ensure a caret is available
+ // below the last line of an import
+ $('body').append("<p></p>");
+ var doc = $('html')[0];
apiLogger.debug('html:');
apiLogger.debug(html);
@@ -38,10 +37,10 @@ function setPadHTML(pad, html, callback)
// using the content collector object
var cc = contentcollector.makeContentCollector(true, null, pad.pool);
try{ // we use a try here because if the HTML is bad it will blow up
- cc.collectContent(doc.childNodes[0]);
+ cc.collectContent(doc);
}catch(e){
apiLogger.warn("HTML was not properly formed", e);
- return; // We don't process the HTML because it was bad..
+ return callback(e); // We don't process the HTML because it was bad..
}
var result = cc.finish();
@@ -92,6 +91,7 @@ function setPadHTML(pad, html, callback)
apiLogger.debug('The changeset: ' + theChangeset);
pad.setText("");
pad.appendRevision(theChangeset);
+ callback(null);
}
exports.setPadHTML = setPadHTML;
diff --git a/src/node/utils/Minify.js b/src/node/utils/Minify.js
index 58d08b30..ba45ab75 100644
--- a/src/node/utils/Minify.js
+++ b/src/node/utils/Minify.js
@@ -23,12 +23,12 @@ var ERR = require("async-stacktrace");
var settings = require('./Settings');
var async = require('async');
var fs = require('fs');
-var cleanCSS = require('clean-css');
+var CleanCSS = require('clean-css');
var jsp = require("uglify-js").parser;
var pro = require("uglify-js").uglify;
var path = require('path');
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
-var RequireKernel = require('require-kernel');
+var RequireKernel = require('etherpad-require-kernel');
var urlutil = require('url');
var ROOT_DIR = path.normalize(__dirname + "/../../static/");
@@ -145,7 +145,6 @@ function minify(req, res, next)
filename = path.normalize(path.join(ROOT_DIR, filename));
if (filename.indexOf(ROOT_DIR) == 0) {
filename = filename.slice(ROOT_DIR.length);
- filename = filename.replace(/\\/g, '/'); // Windows (safe generally?)
} else {
res.writeHead(404, {});
res.end();
@@ -261,7 +260,6 @@ function getAceFile(callback) {
// them into the file.
async.forEach(founds, function (item, callback) {
var filename = item.match(/"([^"]*)"/)[1];
- var request = require('request');
var baseURI = 'http://localhost:' + settings.port;
var resourceURI = baseURI + path.normalize(path.join('/static/', filename));
@@ -411,7 +409,8 @@ function compressJS(values)
function compressCSS(values)
{
var complete = values.join("\n");
- return cleanCSS.process(complete);
+ var minimized = new CleanCSS().minify(complete).styles;
+ return minimized;
}
exports.minify = minify;
diff --git a/src/node/utils/RemoteAddress.js b/src/node/utils/RemoteAddress.js
new file mode 100644
index 00000000..86a4a5b2
--- /dev/null
+++ b/src/node/utils/RemoteAddress.js
@@ -0,0 +1 @@
+exports.remoteAddress = {};
diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js
index c455617b..7e0e6c5a 100644
--- a/src/node/utils/Settings.js
+++ b/src/node/utils/Settings.js
@@ -27,7 +27,7 @@ var npm = require("npm/lib/npm.js");
var jsonminify = require("jsonminify");
var log4js = require("log4js");
var randomString = require("./randomstring");
-
+var suppressDisableMsg = " -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n";
/* Root path of the installation */
exports.root = path.normalize(path.join(npm.dir, ".."));
@@ -55,6 +55,11 @@ exports.ip = "0.0.0.0";
exports.port = process.env.PORT || 9001;
/**
+ * Should we suppress Error messages from being in Pad Contents
+ */
+exports.suppressErrorsInPadText = false;
+
+/**
* The SSL signed server key and the Certificate Authority's own certificate
* default case: ep-lite does *not* use SSL. A signed server key is not required in this case.
*/
@@ -95,7 +100,7 @@ exports.toolbar = {
["showusers"]
],
timeslider: [
- ["timeslider_export", "timeslider_returnToPad"]
+ ["timeslider_export", "timeslider_settings", "timeslider_returnToPad"]
]
}
@@ -130,6 +135,11 @@ exports.minify = true;
exports.abiword = null;
/**
+ * Should we support none natively supported file types on import?
+ */
+exports.allowUnknownFileEnds = true;
+
+/**
* The log level of log4js
*/
exports.loglevel = "INFO";
@@ -139,6 +149,11 @@ exports.loglevel = "INFO";
*/
exports.disableIPlogging = false;
+/**
+ * Disable Load Testing
+ */
+exports.loadTest = false;
+
/*
* log4js appender configuration
*/
@@ -174,6 +189,29 @@ exports.abiwordAvailable = function()
}
};
+// Provide git version if available
+exports.getGitCommit = function() {
+ var version = "";
+ try
+ {
+ var rootPath = path.resolve(npm.dir, '..');
+ var ref = fs.readFileSync(rootPath + "/.git/HEAD", "utf-8");
+ var refPath = rootPath + "/.git/" + ref.substring(5, ref.indexOf("\n"));
+ version = fs.readFileSync(refPath, "utf-8");
+ version = version.substring(0, 7);
+ }
+ catch(e)
+ {
+ console.warn("Can't get git version for server header\n" + e.message)
+ }
+ return version;
+}
+
+// Return etherpad version from package.json
+exports.getEpVersion = function() {
+ return require('ep_etherpad-lite/package.json').version;
+}
+
exports.reloadSettings = function reloadSettings() {
// Discover where the settings file lives
var settingsFilename = argv.settings || "settings.json";
@@ -228,17 +266,45 @@ exports.reloadSettings = function reloadSettings() {
log4js.configure(exports.logconfig);//Configure the logging appenders
log4js.setGlobalLogLevel(exports.loglevel);//set loglevel
+ process.env['DEBUG'] = 'socket.io:' + exports.loglevel; // Used by SocketIO for Debug
log4js.replaceConsole();
+ if(exports.abiword){
+ // Check abiword actually exists
+ if(exports.abiword != null)
+ {
+ fs.exists(exports.abiword, function(exists) {
+ if (!exists) {
+ var abiwordError = "Abiword does not exist at this path, check your settings file";
+ if(!exports.suppressErrorsInPadText){
+ exports.defaultPadText = exports.defaultPadText + "\nError: " + abiwordError + suppressDisableMsg;
+ }
+ console.error(abiwordError);
+ exports.abiword = null;
+ }
+ });
+ }
+ }
+
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");
+ var sessionWarning = "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.suppressErrorsInPadText){
+ exports.defaultPadText = exports.defaultPadText + "\nWarning: " + sessionWarning + suppressDisableMsg;
+ }
+ console.warn(sessionWarning);
}
if(exports.dbType === "dirty"){
- console.warn("DirtyDB is used. This is fine for testing but not recommended for production.");
+ var dirtyWarning = "DirtyDB is used. This is fine for testing but not recommended for production.";
+ if(!exports.suppressErrorsInPadText){
+ exports.defaultPadText = exports.defaultPadText + "\nWarning: " + dirtyWarning + suppressDisableMsg;
+ }
+ console.warn(dirtyWarning);
}
};
// initially load settings
exports.reloadSettings();
+
+
diff --git a/src/node/utils/caching_middleware.js b/src/node/utils/caching_middleware.js
index d30dc398..97134356 100644
--- a/src/node/utils/caching_middleware.js
+++ b/src/node/utils/caching_middleware.js
@@ -19,7 +19,6 @@ var Buffer = require('buffer').Buffer;
var fs = require('fs');
var path = require('path');
var zlib = require('zlib');
-var util = require('util');
var settings = require('./Settings');
var semver = require('semver');
diff --git a/src/node/utils/padDiff.js b/src/node/utils/padDiff.js
index 88fa5cba..24d5bb0c 100644
--- a/src/node/utils/padDiff.js
+++ b/src/node/utils/padDiff.js
@@ -101,8 +101,12 @@ PadDiff.prototype._createClearStartAtext = function(rev, callback){
return callback(err);
}
+ try {
//apply the clearAuthorship changeset
var newAText = Changeset.applyToAText(changeset, atext, self._pad.pool);
+ } catch(err) {
+ return callback(err)
+ }
callback(null, newAText);
});
@@ -209,10 +213,14 @@ PadDiff.prototype._createDiffAtext = function(callback) {
if(superChangeset){
var deletionChangeset = self._createDeletionChangeset(superChangeset,atext,self._pad.pool);
- //apply the superChangeset, which includes all addings
- atext = Changeset.applyToAText(superChangeset,atext,self._pad.pool);
- //apply the deletionChangeset, which adds a deletions
- atext = Changeset.applyToAText(deletionChangeset,atext,self._pad.pool);
+ try {
+ //apply the superChangeset, which includes all addings
+ atext = Changeset.applyToAText(superChangeset,atext,self._pad.pool);
+ //apply the deletionChangeset, which adds a deletions
+ atext = Changeset.applyToAText(deletionChangeset,atext,self._pad.pool);
+ } catch(err) {
+ return callback(err)
+ }
}
callback(err, atext);
diff --git a/src/node/utils/tar.json b/src/node/utils/tar.json
index 70001f8f..05d764a7 100644
--- a/src/node/utils/tar.json
+++ b/src/node/utils/tar.json
@@ -2,6 +2,7 @@
"pad.js": [
"pad.js"
, "pad_utils.js"
+ , "browser.js"
, "pad_cookie.js"
, "pad_editor.js"
, "pad_editbar.js"
@@ -24,6 +25,7 @@
, "colorutils.js"
, "draggable.js"
, "pad_utils.js"
+ , "browser.js"
, "pad_cookie.js"
, "pad_editor.js"
, "pad_editbar.js"
@@ -42,6 +44,7 @@
]
, "ace2_inner.js": [
"ace2_inner.js"
+ , "browser.js"
, "AttributePool.js"
, "Changeset.js"
, "ChangesetUtils.js"
@@ -58,6 +61,7 @@
]
, "ace2_common.js": [
"ace2_common.js"
+ , "browser.js"
, "jquery.js"
, "rjquery.js"
, "$async.js"
diff --git a/src/node/utils/toolbar.js b/src/node/utils/toolbar.js
index e8d02dd6..07b86496 100644
--- a/src/node/utils/toolbar.js
+++ b/src/node/utils/toolbar.js
@@ -4,7 +4,6 @@
var _ = require("underscore")
, tagAttributes
, tag
- , defaultButtons
, Button
, ButtonsGroup
, Separator
@@ -100,12 +99,14 @@ _.extend(Button.prototype, {
};
return tag("li", liAttributes,
tag("a", { "class": this.grouping, "data-l10n-id": this.attributes.localizationId },
- tag("span", { "class": " "+ this.attributes.class })
+ tag("button", { "class": " "+ this.attributes.class, "data-l10n-id": this.attributes.localizationId })
)
);
}
});
+
+
SelectButton = function (attributes) {
this.attributes = attributes;
this.options = [];
@@ -122,8 +123,7 @@ _.extend(SelectButton.prototype, Button.prototype, {
},
select: function (attributes) {
- var self = this
- , options = [];
+ var options = [];
_.each(this.options, function (opt) {
var a = _.extend({
@@ -210,6 +210,12 @@ module.exports = {
class: "buttonicon buttonicon-import_export"
},
+ timeslider_settings: {
+ command: "settings",
+ localizationId: "pad.toolbar.settings.title",
+ class: "buttonicon buttonicon-settings"
+ },
+
timeslider_returnToPad: {
command: "timeslider_returnToPad",
localizationId: "timeslider.toolbar.returnbutton",