Merge branch 'master' of git://github.com/ether/etherpad-lite into create_pad_special_characters

This commit is contained in:
Stefan 2015-04-11 12:10:37 +02:00
commit aa0d14c7d7
237 changed files with 6386 additions and 1927 deletions

View file

@ -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 */
/*****************/
@ -512,6 +544,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
@ -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,

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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");

View file

@ -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();
});

View file

@ -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");

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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
{

View file

@ -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);
});
}

View file

@ -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) {

View file

@ -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;

View file

@ -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 )
};

View file

@ -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;
})
}
}

View file

@ -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) {

View file

@ -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();

View file

@ -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

View file

@ -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);

View file

@ -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) {

View file

@ -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);

View file

@ -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;
}

View file

@ -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')

View file

@ -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

View file

@ -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");

View file

@ -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;
}

View file

@ -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);
});
}

View file

@ -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]);

View file

@ -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 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; }' +
var foot = '</body>\n</html>\n';
'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;}' +
getPadHTML(pad, revNum, function (err, html)
{
if(ERR(err, callback)) return;
callback(null, head + html + foot);
'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/;

View file

@ -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);

View file

@ -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);
});
}

View file

@ -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;

View file

@ -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;

View file

@ -0,0 +1 @@
exports.remoteAddress = {};

View file

@ -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, ".."));
@ -54,6 +54,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"]
]
}
@ -129,6 +134,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
*/
@ -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();

View file

@ -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');

View file

@ -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);

View file

@ -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"

View file

@ -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",