Commit 24b0712d authored by Stefan's avatar Stefan

Merge new release into master branch!

parents cc34f4e3 64d94cb3
......@@ -2,6 +2,7 @@ node_modules
settings.json
!settings.json.template
APIKEY.txt
SESSIONKEY.txt
bin/abiword.exe
bin/node.exe
etherpad-lite-win.zip
......
# 1.5.5
* SECURITY: Also don't allow read files on directory traversal on minify paths
* NEW: padOptions can be set in settings.json now
* Fix: Add check for special characters in createPad API function
* Fix: Middle click on a link in firefox don't paste text anymore
* Fix: Made setPadRaw async to import larger etherpad files
* Fix: rtl
* Fix: Problem in older IEs
* Other: Update to express 4.x
* Other: Dropped support for node 0.8
* Other: Update ejs to version 2.x
* Other: Moved sessionKey from settings.json to a new auto-generated SESSIONKEY.txt file
# 1.5.4
* SECURITY: Also don't allow read files on directory traversal on frontend tests path
......
......@@ -22,7 +22,7 @@ Also, check out the **[FAQ](https://github.com/ether/etherpad-lite/wiki/FAQ)**,
# Installation
Etherpad works with node v0.8, v0.10 and v0.11, only. (We don't support v0.6)
Etherpad works with node v0.10+ and io.js.
## Windows
......
#!/bin/sh
NODE_VERSION="0.8.4"
NODE_VERSION="0.10.38"
#Move to the folder where ep-lite is installed
cd `dirname $0`
......
......@@ -50,9 +50,9 @@ NODE_V_MINOR=$(echo $NODE_VERSION | cut -d "." -f 1-2)
if hash iojs 2>/dev/null; then
IOJS_VERSION=$(iojs --version)
fi
if [ ! $NODE_V_MINOR = "v0.8" ] && [ ! $NODE_V_MINOR = "v0.10" ] && [ ! $NODE_V_MINOR = "v0.11" ] && [ ! $NODE_V_MINOR = "v0.12" ]; then
if [ ! $NODE_V_MINOR = "v0.10" ] && [ ! $NODE_V_MINOR = "v0.11" ] && [ ! $NODE_V_MINOR = "v0.12" ]; then
if [ ! $IOJS_VERSION ]; then
echo "You're running a wrong version of node, or io.js is not installed. You're using $NODE_VERSION, we need v0.8.x, v0.10.x, v0.11.x or v0.12.x" >&2
echo "You're running a wrong version of node, or io.js is not installed. You're using $NODE_VERSION, we need v0.10.x, v0.11.x or v0.12.x" >&2
exit 1
fi
fi
......
......@@ -8,7 +8,7 @@ cmd /C node -e "" || ( echo "Please install node.js ( http://nodejs.org )" && ex
echo _
echo Checking node version...
set check_version="if(['8','10'].indexOf(process.version.split('.')[1].toString()) === -1) { console.log('You are running a wrong version of Node. Etherpad requires v0.8.x or v0.10.x'); process.exit(1) }"
set check_version="if(['10','11','12'].indexOf(process.version.split('.')[1]) === -1 && process.version.split('.')[0] !== '1') { console.log('You are running a wrong version of Node. Etherpad requires v0.10+'); process.exit(1) }"
cmd /C node -e %check_version% || exit /B 1
echo _
......
......@@ -61,7 +61,7 @@ Portal submits content into new blog post
## Usage
### API version
The latest version is `1.2.11`
The latest version is `1.2.12`
The current version can be queried via /api.
......@@ -232,7 +232,7 @@ creates a new session. validUntil is an unix timestamp in seconds
deletes a session
*Example returns:*
* `{code: 1, message:"ok", data: null}`
* `{code: 0, message:"ok", data: null}`
* `{code: 1, message:"sessionID does not exist", data: null}`
#### getSessionInfo(sessionID)
......@@ -388,10 +388,12 @@ Group pads are normal pads, but with the name schema GROUPID$PADNAME. A security
* API >= 1
creates a new (non-group) pad. Note that if you need to create a group Pad, you should call **createGroupPad**.
You get an error message if you use one of the following characters in the padID: "/", "?", "&" or "#".
*Example returns:*
* `{code: 0, message:"ok", data: null}`
* `{code: 1, message:"pad does already exist", data: null}`
* `{code: 1, message:"padID does already exist", data: null}`
* `{code: 1, message:"malformed padID: Remove special characters", data: null}`
#### getRevisionsCount(padID)
* API >= 1
......
......@@ -15,10 +15,6 @@
"ip": "0.0.0.0",
"port" : 9001,
// Session Key, used for reconnecting user sessions
// Set this to a secure string at least 10 characters long. Do not share this value.
"sessionKey" : "",
/*
// Node native SSL support
// this is disabled by default
......@@ -53,6 +49,21 @@
//the default text of a pad
"defaultPadText" : "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at http:\/\/etherpad.org\n",
/* Default Pad behavior, users can override by changing */
"padOptions": {
"noColors": false,
"showControls": true,
"showChat": true,
"showLineNumbers": true,
"useMonospaceFont": false,
"userName": false,
"userColor": false,
"rtl": false,
"alwaysShowChat": false,
"chatAndUsers": false,
"lang": "en-gb"
},
/* Shoud we suppress errors from being visible in the default Pad Text? */
"suppressErrorsInPadText" : false,
......
......@@ -687,12 +687,21 @@ Example returns:
exports.createPad = function(padID, text, callback)
{
//ensure there is no $ in the padID
if(padID && padID.indexOf("$") != -1)
if(padID)
{
callback(new customError("createPad can't create group pads","apierror"));
return;
if(padID.indexOf("$") != -1)
{
callback(new customError("createPad can't create group pads","apierror"));
return;
}
//check for url special characters
else if(padID.match(/(\/|\?|&|#)/))
{
callback(new customError("malformed padID: Remove special characters","apierror"));
return;
}
}
//create pad
getPadSafe(padID, false, text, function(err)
{
......
......@@ -4,7 +4,7 @@
* This is not used for authors that are created via the API at current
*/
var Store = require('ep_etherpad-lite/node_modules/connect/lib/middleware/session/store'),
var Store = require('ep_etherpad-lite/node_modules/express-session').Store,
db = require('ep_etherpad-lite/node/db/DB').db,
log4js = require('ep_etherpad-lite/node_modules/log4js'),
messageLogger = log4js.getLogger("SessionStore");
......
......@@ -26,7 +26,7 @@ var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
var resolve = require("resolve");
exports.info = {
buf_stack: [],
__output_stack: [],
block_stack: [],
file_stack: [],
args: []
......@@ -41,27 +41,27 @@ function createBlockId(name) {
}
exports._init = function (b, recursive) {
exports.info.buf_stack.push(exports.info.buf);
exports.info.buf = b;
exports.info.__output_stack.push(exports.info.__output);
exports.info.__output = b;
}
exports._exit = function (b, recursive) {
getCurrentFile().inherit.forEach(function (item) {
exports._require(item.name, item.args);
});
exports.info.buf = exports.info.buf_stack.pop();
exports.info.__output = exports.info.__output_stack.pop();
}
exports.begin_capture = function() {
exports.info.buf_stack.push(exports.info.buf.concat());
exports.info.buf.splice(0, exports.info.buf.length);
exports.info.__output_stack.push(exports.info.__output.concat());
exports.info.__output.splice(0, exports.info.__output.length);
}
exports.end_capture = function () {
var res = exports.info.buf.join("");
exports.info.buf.splice.apply(
exports.info.buf,
[0, exports.info.buf.length].concat(exports.info.buf_stack.pop()));
var res = exports.info.__output.join("");
exports.info.__output.splice.apply(
exports.info.__output,
[0, exports.info.__output.length].concat(exports.info.__output_stack.pop()));
return res;
}
......@@ -80,7 +80,7 @@ exports.end_block = function () {
var renderContext = exports.info.args[exports.info.args.length-1];
var args = {content: exports.end_define_block(), renderContext: renderContext};
hooks.callAll("eejsBlock_" + name, args);
exports.info.buf.push(args.content);
exports.info.__output.push(args.content);
}
exports.begin_block = exports.begin_define_block;
......@@ -114,7 +114,7 @@ exports.require = function (name, args, mod) {
args.e = exports;
args.require = require;
var template = '<% e._init(buf); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>';
var template = '<% e._init(__output); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>';
exports.info.args.push(args);
exports.info.file_stack.push({path: ejspath, inherit: []});
......@@ -127,5 +127,5 @@ exports.require = function (name, args, mod) {
}
exports._require = function (name, args) {
exports.info.buf.push(exports.require(name, args));
exports.info.__output.push(exports.require(name, args));
}
......@@ -103,7 +103,7 @@ exports.doExport = function(req, res, padId, type)
//send the file
function(callback)
{
res.sendfile(destFile, null, callback);
res.sendFile(destFile, null, callback);
},
//clean up temporary files
function(callback)
......@@ -184,7 +184,7 @@ exports.doExport = function(req, res, padId, type)
//send the file
function(callback)
{
res.sendfile(destFile, null, callback);
res.sendFile(destFile, null, callback);
},
//clean up temporary files
function(callback)
......
......@@ -113,10 +113,8 @@ exports.doImport = function(req, res, padId)
if(ERR(err, callback)) return callback();
if(result.length > 0){ // This feels hacky and wrong..
importHandledByPlugin = true;
callback();
}else{
callback();
}
callback();
});
},
function(callback) {
......@@ -145,7 +143,7 @@ exports.doImport = function(req, res, padId)
},
//convert file to html
function(callback) {
if(!importHandledByPlugin || !directDatabaseAccess){
if(!importHandledByPlugin && !directDatabaseAccess){
var fileEnding = path.extname(srcFile).toLowerCase();
var fileIsHTML = (fileEnding === ".html" || fileEnding === ".htm");
var fileIsTXT = (fileEnding === ".txt");
......@@ -171,28 +169,24 @@ exports.doImport = function(req, res, padId)
},
function(callback) {
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 (!abiword && !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");
}
});
}else{
callback();
}
}
if (isAscii) {
callback();
} else {
callback("uploadFailed");
}
});
} else {
callback();
}
......@@ -303,7 +297,7 @@ exports.doImport = function(req, res, padId)
var impexp = window.parent.padimpexp.handleFrameCall('" + directDatabaseAccess +"', '" + status + "'); \
}) \
</script>"
, 200);
);
});
}
......@@ -1182,6 +1182,7 @@ function handleClientReady(client, message)
"userIsGuest": true,
"userColor": authorColorId,
"padId": message.padId,
"padOptions": settings.padOptions,
"initialTitle": "Pad: " + message.padId,
"opts": {},
// tell the client the number of the latest chat-message, which will be
......
......@@ -69,10 +69,8 @@ exports.restartServer = function () {
if(settings.trustProxy){
app.enable('trust proxy');
}
app.configure(function() {
hooks.callAll("expressConfigure", {"app": app});
});
hooks.callAll("expressConfigure", {"app": app});
hooks.callAll("expressCreateServer", {"app": app, "server": server});
server.listen(settings.port, settings.ip);
......
......@@ -39,7 +39,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// if an error occurs Connect will pass it down
// through these "error-handling" middleware
// allowing you to respond however you like
res.send(500, { error: 'Sorry, something bad happened!' });
res.status(500).send({ error: 'Sorry, something bad happened!' });
console.error(err.stack? err.stack : err.toString());
stats.meter('http500').mark()
})
......@@ -50,4 +50,4 @@ exports.expressCreateServer = function (hook_name, args, cb) {
//https://github.com/joyent/node/issues/1553
process.on('SIGINT', exports.gracefulShutdown);
}
}
\ No newline at end of file
}
......@@ -55,7 +55,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
ERR(err);
if(err == "notfound")
res.send(404, '404 - Not Found');
res.status(404).send('404 - Not Found');
else
res.send(html);
});
......
......@@ -7,7 +7,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
//ensure the padname is valid and the url doesn't end with a /
if(!padManager.isValidPadId(padId) || /\/$/.test(req.url))
{
res.send(404, 'Such a padname is forbidden');
res.status(404).send('Such a padname is forbidden');
}
else
{
......@@ -19,7 +19,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
var query = url.parse(req.url).query;
if ( query ) real_url += '?' + query;
res.header('Location', real_url);
res.send(302, 'You should be redirected to <a href="' + real_url + '">' + real_url + '</a>');
res.status(302).send('You should be redirected to <a href="' + real_url + '">' + real_url + '</a>');
}
//the pad id was fine, so just render it
else
......
......@@ -6,7 +6,8 @@ var webaccess = require("ep_etherpad-lite/node/hooks/express/webaccess");
var padMessageHandler = require("../../handler/PadMessageHandler");
var connect = require('connect');
var cookieParser = require('cookie-parser');
var sessionModule = require('express-session');
exports.expressCreateServer = function (hook_name, args, cb) {
//init socket.io and redirect all requests to the MessageHandler
......@@ -20,6 +21,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
/* Require an express session cookie to be present, and load the
* session. See http://www.danielbaulig.de/socket-ioexpress for more
* info */
var cookieParserFn = cookieParser(webaccess.secret, {});
io.use(function(socket, accept) {
var data = socket.request;
......@@ -29,8 +31,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
}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){
cookieParserFn(data, {}, function(err){
if(err) {
console.error(err);
accept("Couldn't parse request cookies. ", false);
......@@ -40,7 +41,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
data.sessionID = data.signedCookies.express_sid;
args.app.sessionStore.get(data.sessionID, function (err, session) {
if (err || !session) return accept('Bad session / session has expired', false);
data.session = new connect.middleware.session.Session(data, session);
data.session = new sessionModule.Session(data, session);
accept(null, true);
});
});
......
......@@ -19,13 +19,13 @@ exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/robots.txt', function(req, res)
{
var filePath = path.normalize(__dirname + "/../../../static/custom/robots.txt");
res.sendfile(filePath, function(err)
res.sendFile(filePath, function(err)
{
//there is no custom favicon, send the default robots.txt which dissallows all
if(err)
{
filePath = path.normalize(__dirname + "/../../../static/robots.txt");
res.sendfile(filePath);
res.sendFile(filePath);
}
});
});
......@@ -60,13 +60,13 @@ exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get( /\/favicon.ico$/, function(req, res)
{
var filePath = path.normalize(__dirname + "/../../../static/custom/favicon.ico");
res.sendfile(filePath, function(err)
res.sendFile(filePath, function(err)
{
//there is no custom favicon, send the default favicon
if(err)
{
filePath = path.normalize(__dirname + "/../../../static/favicon.ico");
res.sendfile(filePath);
res.sendFile(filePath);
}
});
});
......
......@@ -9,7 +9,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// Cache both minified and static.
var assetCache = new CachingMiddleware;
args.app.all('/(javascripts|static)/*', assetCache.handle);
args.app.all(/\/javascripts\/(.*)/, assetCache.handle);
// Minify will serve static files compressed (minify enabled). It also has
// file-specific hacks for ace/require-kernel/etc.
......@@ -30,7 +30,8 @@ exports.expressCreateServer = function (hook_name, args, cb) {
Yajsml.associators.associationsForSimpleMapping(minify.tar);
var associator = new StaticAssociator(associations);
jsServer.setAssociator(associator);
args.app.use(jsServer);
args.app.use(jsServer.handle.bind(jsServer));
// serve plugin definitions
// not very static, but served here so that client can do require("pluginfw/static/js/plugin-definitions.js");
......
......@@ -57,7 +57,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/tests/frontend/*', function (req, res) {
var filePath = url2FilePath(req.url);
res.sendfile(filePath);
res.sendFile(filePath);
});
args.app.get('/tests/frontend', function (req, res) {
......
......@@ -4,7 +4,8 @@ var httpLogger = log4js.getLogger("http");
var settings = require('../../utils/Settings');
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var ueberStore = require('../../db/SessionStore');
var stats = require('ep_etherpad-lite/node/stats')
var stats = require('ep_etherpad-lite/node/stats');
var sessionModule = require('express-session');
//checks for basic http auth
exports.basicAuth = function (req, res, next) {
......@@ -56,10 +57,10 @@ exports.basicAuth = function (req, res, next) {
res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
if (req.headers.authorization) {
setTimeout(function () {
res.send(401, 'Authentication required');
res.status(401).send('Authentication required');
}, 1000);
} else {
res.send(401, 'Authentication required');
res.status(401).send('Authentication required');
}
}));
}
......@@ -117,9 +118,8 @@ exports.expressConfigure = function (hook_name, args, cb) {
exports.secret = settings.sessionKey; // Isn't this being reset each time the server spawns?
}
args.app.use(express.cookieParser(exports.secret));
args.app.sessionStore = exports.sessionStore;
args.app.use(express.session({secret: exports.secret, store: args.app.sessionStore, key: 'express_sid' }));
args.app.use(sessionModule({secret: exports.secret, store: args.app.sessionStore, resave: true, saveUninitialized: true, name: 'express_sid' }));
args.app.use(exports.basicAuth);
}
......
......@@ -91,7 +91,7 @@ exports.expressCreateServer = function(n, args) {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.send('{"'+locale+'":'+JSON.stringify(locales[locale])+'}');
} else {
res.send(404, 'Language not available');
res.status(404).send('Language not available');
}
})
......
......@@ -15,7 +15,7 @@ module.exports = function (req, res, callback) {
callback();
//no access
} else {
res.send(403, "403 - Can't touch this");
res.status(403).send("403 - Can't touch this");
}
});
}
......@@ -21,20 +21,11 @@ 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.
return setImmediate(cb);
}
// Author data
......@@ -76,7 +67,7 @@ exports.setPadRaw = function(padId, records, callback){
// Write the value to the server
db.set(newKey, value);
cb();
setImmediate(cb);
}, function(){
callback(null, true);
});
......
......@@ -165,7 +165,6 @@ function minify(req, res, next)
var plugin = plugins.plugins[library];
var pluginPath = plugin.package.realPath;
filename = path.relative(ROOT_DIR, pluginPath + libraryPath);
filename = filename.replace(/\\/g, '/'); // Windows (safe generally?)
} else if (LIBRARY_WHITELIST.indexOf(library) != -1) {
// Go straight into node_modules
// Avoid `require.resolve()`, since 'mustache' and 'mustache/index.js'
......
......@@ -28,6 +28,7 @@ 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";
var _ = require("underscore");
/* Root path of the installation */