diff --git a/.gitignore b/.gitignore index c6ec3f9f..52d60dca 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 926e3d3c..82c1e117 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# 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 diff --git a/README.md b/README.md index 3d266827..bef84656 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/bin/buildForWindows.sh b/bin/buildForWindows.sh index 78441ba0..212e946b 100755 --- a/bin/buildForWindows.sh +++ b/bin/buildForWindows.sh @@ -1,6 +1,6 @@ #!/bin/sh -NODE_VERSION="0.8.4" +NODE_VERSION="0.10.38" #Move to the folder where ep-lite is installed cd `dirname $0` diff --git a/bin/installDeps.sh b/bin/installDeps.sh index a5e4d5ab..f2a3aafc 100755 --- a/bin/installDeps.sh +++ b/bin/installDeps.sh @@ -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 diff --git a/bin/installOnWindows.bat b/bin/installOnWindows.bat index 86223064..9b9a42e4 100644 --- a/bin/installOnWindows.bat +++ b/bin/installOnWindows.bat @@ -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 _ diff --git a/doc/api/http_api.md b/doc/api/http_api.md index 2ae674d8..59510a75 100644 --- a/doc/api/http_api.md +++ b/doc/api/http_api.md @@ -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 diff --git a/settings.json.template b/settings.json.template index ea7ed1e1..39c383ed 100644 --- a/settings.json.template +++ b/settings.json.template @@ -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, diff --git a/src/node/db/API.js b/src/node/db/API.js index edd130e2..97d5162d 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -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) { diff --git a/src/node/db/SessionStore.js b/src/node/db/SessionStore.js index 5c45ddb3..97404690 100644 --- a/src/node/db/SessionStore.js +++ b/src/node/db/SessionStore.js @@ -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"); diff --git a/src/node/eejs/index.js b/src/node/eejs/index.js index 30f5a442..9d032840 100644 --- a/src/node/eejs/index.js +++ b/src/node/eejs/index.js @@ -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)); } diff --git a/src/node/handler/ExportHandler.js b/src/node/handler/ExportHandler.js index 0654deb4..f20e8715 100644 --- a/src/node/handler/ExportHandler.js +++ b/src/node/handler/ExportHandler.js @@ -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) diff --git a/src/node/handler/ImportHandler.js b/src/node/handler/ImportHandler.js index 2dad8b3d..7ad82a87 100644 --- a/src/node/handler/ImportHandler.js +++ b/src/node/handler/ImportHandler.js @@ -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 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 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 + "'); \ }) \ " - , 200); + ); }); } diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index c210ab2b..e15af1a4 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -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 diff --git a/src/node/hooks/express.js b/src/node/hooks/express.js index bf849419..3abe41f8 100644 --- a/src/node/hooks/express.js +++ b/src/node/hooks/express.js @@ -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); diff --git a/src/node/hooks/express/errorhandling.js b/src/node/hooks/express/errorhandling.js index 087dd50e..7afe80ae 100644 --- a/src/node/hooks/express/errorhandling.js +++ b/src/node/hooks/express/errorhandling.js @@ -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 +} diff --git a/src/node/hooks/express/padreadonly.js b/src/node/hooks/express/padreadonly.js index d60d3863..66be3339 100644 --- a/src/node/hooks/express/padreadonly.js +++ b/src/node/hooks/express/padreadonly.js @@ -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); }); diff --git a/src/node/hooks/express/padurlsanitize.js b/src/node/hooks/express/padurlsanitize.js index 2aadccdc..94cbe36a 100644 --- a/src/node/hooks/express/padurlsanitize.js +++ b/src/node/hooks/express/padurlsanitize.js @@ -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 ' + real_url + ''); + res.status(302).send('You should be redirected to ' + real_url + ''); } //the pad id was fine, so just render it else diff --git a/src/node/hooks/express/socketio.js b/src/node/hooks/express/socketio.js index 35d6d074..23622f3a 100644 --- a/src/node/hooks/express/socketio.js +++ b/src/node/hooks/express/socketio.js @@ -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); }); }); diff --git a/src/node/hooks/express/specialpages.js b/src/node/hooks/express/specialpages.js index 063328fb..0370c4fc 100644 --- a/src/node/hooks/express/specialpages.js +++ b/src/node/hooks/express/specialpages.js @@ -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); } }); }); diff --git a/src/node/hooks/express/static.js b/src/node/hooks/express/static.js index e5a2bff0..34fce29e 100644 --- a/src/node/hooks/express/static.js +++ b/src/node/hooks/express/static.js @@ -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"); diff --git a/src/node/hooks/express/tests.js b/src/node/hooks/express/tests.js index 151c99fa..d0dcc0cc 100644 --- a/src/node/hooks/express/tests.js +++ b/src/node/hooks/express/tests.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) { diff --git a/src/node/hooks/express/webaccess.js b/src/node/hooks/express/webaccess.js index b798f2c7..cb5a2207 100644 --- a/src/node/hooks/express/webaccess.js +++ b/src/node/hooks/express/webaccess.js @@ -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); } diff --git a/src/node/hooks/i18n.js b/src/node/hooks/i18n.js index 67815659..1d28b544 100644 --- a/src/node/hooks/i18n.js +++ b/src/node/hooks/i18n.js @@ -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'); } }) diff --git a/src/node/padaccess.js b/src/node/padaccess.js index d8780914..97333514 100644 --- a/src/node/padaccess.js +++ b/src/node/padaccess.js @@ -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"); } }); } diff --git a/src/node/utils/ImportEtherpad.js b/src/node/utils/ImportEtherpad.js index 37863bff..bf1129cb 100644 --- a/src/node/utils/ImportEtherpad.js +++ b/src/node/utils/ImportEtherpad.js @@ -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); }); diff --git a/src/node/utils/Minify.js b/src/node/utils/Minify.js index ba45ab75..3b0be38c 100644 --- a/src/node/utils/Minify.js +++ b/src/node/utils/Minify.js @@ -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' diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js index 7e0e6c5a..b7d1f0bc 100644 --- a/src/node/utils/Settings.js +++ b/src/node/utils/Settings.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 */ exports.root = path.normalize(path.join(npm.dir, "..")); @@ -84,6 +85,23 @@ exports.dbSettings = { "filename" : path.join(exports.root, "dirty.db") }; */ exports.defaultPadText = "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nEtherpad on Github: http:\/\/j.mp/ep-lite\n"; +/** + * The default Pad Settings for a user (Can be overridden by changing the setting + */ +exports.padOptions = { + "noColors": false, + "showControls": true, + "showChat": true, + "showLineNumbers": true, + "useMonospaceFont": false, + "userName": false, + "userColor": false, + "rtl": false, + "alwaysShowChat": false, + "chatAndUsers": false, + "lang": "en-gb" +} + /** * The toolbar buttons and order. */ @@ -255,7 +273,11 @@ exports.reloadSettings = function reloadSettings() { //or it's a settings hash, specific to a plugin if(exports[i] !== undefined || i.indexOf('ep_')==0) { - exports[i] = settings[i]; + if (_.isObject(settings[i]) && !_.isArray(settings[i])) { + exports[i] = _.defaults(settings[i], exports[i]); + } else { + exports[i] = settings[i]; + } } //this setting is unkown, output a warning and throw it away else @@ -286,13 +308,15 @@ exports.reloadSettings = function reloadSettings() { } } - if(!exports.sessionKey){ // If the secretKey isn't set we also create yet another unique value here - exports.sessionKey = randomString(32); - 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; + if (!exports.sessionKey) { + try { + exports.sessionKey = fs.readFileSync("./SESSIONKEY.txt","utf8"); + } catch(e) { + exports.sessionKey = randomString(32); + fs.writeFileSync("./SESSIONKEY.txt",exports.sessionKey,"utf8"); } - console.warn(sessionWarning); + } else { + console.warn("Declaring the sessionKey in the settings.json is deprecated. This value is auto-generated now. Please remove the setting from the file."); } if(exports.dbType === "dirty"){ diff --git a/src/package.json b/src/package.json index ed9ba957..8aed8ffb 100644 --- a/src/package.json +++ b/src/package.json @@ -14,45 +14,46 @@ "dependencies" : { "etherpad-yajsml" : "0.0.2", "request" : "2.55.0", - "etherpad-require-kernel" : "1.0.8", + "etherpad-require-kernel" : "1.0.9", "resolve" : "1.1.6", "socket.io" : "1.3.5", "ueberDB" : "0.2.15", - "express" : "3.8.1", + "express" : "4.12.3", + "express-session" : "1.11.1", + "cookie-parser" : "1.3.4", "async" : "0.9.0", - "connect" : "2.7.11", "clean-css" : "3.1.9", "uglify-js" : "2.4.19", "formidable" : "1.0.17", "log4js" : "0.6.22", "cheerio" : "0.19.0", "async-stacktrace" : "0.0.2", - "npm" : "2.7.5", - "ejs" : "1.0.0", + "npm" : "2.7.6", + "ejs" : "2.3.1", "graceful-fs" : "3.0.6", "slide" : "1.1.6", "semver" : "4.3.3", "security" : "1.0.0", "tinycon" : "0.0.1", - "underscore" : "1.5.1", + "underscore" : "1.8.3", "unorm" : "1.3.3", "languages4translatewiki" : "0.1.3", "swagger-node-express" : "2.1.3", "channels" : "0.0.4", "jsonminify" : "0.2.3", "measured" : "1.0.0", - "mocha" : "2.2.1", + "mocha" : "2.2.4", "supertest" : "0.15.0" }, "bin": { "etherpad-lite": "./node/server.js" }, "devDependencies": { "wd" : "0.3.11" }, - "engines" : { "node" : ">=0.6.3", + "engines" : { "node" : ">=0.10.0", "npm" : ">=1.0" }, "repository" : { "type" : "git", "url" : "http://github.com/ether/etherpad-lite.git" }, - "version" : "1.5.4" + "version" : "1.5.5" } diff --git a/src/static/css/iframe_editor.css b/src/static/css/iframe_editor.css index b708e2f4..b7ece1e6 100644 --- a/src/static/css/iframe_editor.css +++ b/src/static/css/iframe_editor.css @@ -210,8 +210,9 @@ ol { list-style-type: decimal; } +/* Fixes #2223 and #1836 */ ol > li { - display:inline; + display:block; } /* Set the indentation */ diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index cf062d26..1120b2c1 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -1923,7 +1923,11 @@ function Ace2Inner(){ if (charsLeft === 0) { var index = 0; - browser.msie = false; // Temp fix to resolve enter and backspace issues.. + + if (browser.msie && parseInt(browser.version) >= 11) { + browser.msie = false; // Temp fix to resolve enter and backspace issues.. + // Note that this makes MSIE behave like modern browsers.. + } if (browser.msie && line == (rep.lines.length() - 1) && lineNode.childNodes.length === 0) { // best to stay at end of last empty div in IE @@ -4955,7 +4959,10 @@ function Ace2Inner(){ // Don't paste on middle click of links $(root).on("paste", function(e){ - if(e.target.a){ + // TODO: this breaks pasting strings into URLS when using + // Control C and Control V -- the Event is never available + // here.. :( + if(e.target.a || e.target.localName === "a"){ e.preventDefault(); } }) diff --git a/src/static/js/broadcast_slider.js b/src/static/js/broadcast_slider.js index eff20b52..2299bba3 100644 --- a/src/static/js/broadcast_slider.js +++ b/src/static/js/broadcast_slider.js @@ -166,6 +166,7 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) padmodals.showModal("disconnected"); } + // Throttle seems like overkill here... Not sure why we do it! var fixPadHeight = _.throttle(function(){ var height = $('#timeslider-top').height(); $('#editorcontainerbox').css({marginTop: height}); diff --git a/src/static/js/html10n.js b/src/static/js/html10n.js index 19cf9de1..44abd7a0 100644 --- a/src/static/js/html10n.js +++ b/src/static/js/html10n.js @@ -681,9 +681,9 @@ window.html10n = (function(window, document, undefined) { // Expand two-part locale specs var i=0 langs.forEach(function(lang) { - if(!lang) return - langs[i++] = lang - if(~lang.indexOf('-')) langs[i++] = lang.substr(0, lang.indexOf('-')) + if(!lang) return; + langs[i++] = lang; + if(~lang.indexOf('-')) langs[i++] = lang.substr(0, lang.indexOf('-')); }) this.build(langs, function(er, translations) { diff --git a/src/static/js/pad.js b/src/static/js/pad.js index f1de80f0..a2c76c08 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -126,6 +126,18 @@ var getParameters = [ function getParams() { + // Tries server enforced options first.. + for(var i = 0; i < getParameters.length; i++) + { + var setting = getParameters[i]; + var value = clientVars.padOptions[setting.name]; + if(value.toString() === setting.checkVal) + { + setting.callback(value); + } + } + + // Then URL applied stuff var params = getUrlVars() for(var i = 0; i < getParameters.length; i++) @@ -475,7 +487,6 @@ var pad = { { // start the custom js if (typeof customStart == "function") customStart(); - getParams(); handshake(); // To use etherpad you have to allow cookies. @@ -495,6 +506,8 @@ var pad = { //initialize the chat chat.init(this); + getParams(); + padcookie.init(); // initialize the cookies pad.initTime = +(new Date()); pad.padOptions = clientVars.initialOptions; diff --git a/src/static/js/pad_editor.js b/src/static/js/pad_editor.js index b1ea09f7..2514cd12 100644 --- a/src/static/js/pad_editor.js +++ b/src/static/js/pad_editor.js @@ -136,8 +136,6 @@ var padeditor = (function() var v; v = getOption('rtlIsTrue', ('rtl' == html10n.getDirection())); - // Override from parameters if true - if(settings.rtlIsTrue === true) v = true; self.ace.setProperty("rtlIsTrue", v); padutils.setCheckbox($("#options-rtlcheck"), v); diff --git a/tests/backend/specs/api/pad.js b/tests/backend/specs/api/pad.js index 52849c2e..75e77971 100644 --- a/tests/backend/specs/api/pad.js +++ b/tests/backend/specs/api/pad.js @@ -2,7 +2,8 @@ var assert = require('assert') supertest = require(__dirname+'/../../../../src/node_modules/supertest'), fs = require('fs'), api = supertest('http://localhost:9001'); - path = require('path'); + path = require('path'), + async = require(__dirname+'/../../../../src/node_modules/async'); var filePath = path.join(__dirname, '../../../../APIKEY.txt'); @@ -80,6 +81,7 @@ describe('Permission', function(){ -> setHTML(padID) -- Should fail on invalid HTML -> setHTML(padID) *3 -- Should fail on invalid HTML -> getHTML(padID) -- Should return HTML close to posted HTML + -> createPad -- Tries to create pads with bad url characters */ @@ -494,6 +496,23 @@ describe('getHTML', function(){ }); }) +describe('createPad', function(){ + it('errors if pad can be created', function(done) { + var badUrlChars = ["/", "%23", "%3F", "%26"]; + async.map( + badUrlChars, + function (badUrlChar, cb) { + api.get(endPoint('createPad')+"&padID="+badUrlChar) + .expect(function(res){ + if(res.body.code !== 1) throw new Error("Pad with bad characters was created"); + }) + .expect('Content-Type', /json/) + .end(cb); + }, + done); + }); +}) + /* -> movePadForce Test diff --git a/tests/frontend/specs/font_type.js b/tests/frontend/specs/font_type.js index 41b1de34..e5c65f2e 100644 --- a/tests/frontend/specs/font_type.js +++ b/tests/frontend/specs/font_type.js @@ -24,7 +24,8 @@ describe("font select", function(){ //check if font changed to monospace var fontFamily = inner$("body").css("font-family").toLowerCase(); - expect(fontFamily).to.be("courier new"); + var containsStr = fontFamily.indexOf("courier new"); + expect(containsStr).to.not.be(-1); done(); }); diff --git a/tests/frontend/specs/importexport.js b/tests/frontend/specs/importexport.js index 59607dba..2dc002ba 100644 --- a/tests/frontend/specs/importexport.js +++ b/tests/frontend/specs/importexport.js @@ -52,7 +52,7 @@ describe("import functionality", function(){ return exportresults } - it("import a pad with newlines from txt", function(done){ + xit("import a pad with newlines from txt", function(done){ var importurl = helper.padChrome$.window.location.href+'/import' var textWithNewLines = 'imported text\nnewline' importrequest(textWithNewLines,importurl,"txt") @@ -64,7 +64,7 @@ describe("import functionality", function(){ expect(results[1][1]).to.be("imported text\nnewline\n\n") done() }) - it("import a pad with newlines from html", function(done){ + xit("import a pad with newlines from html", function(done){ var importurl = helper.padChrome$.window.location.href+'/import' var htmlWithNewLines = 'htmltext
newline' importrequest(htmlWithNewLines,importurl,"html") @@ -76,7 +76,7 @@ describe("import functionality", function(){ expect(results[1][1]).to.be("htmltext\nnewline\n\n") done() }) - it("import a pad with attributes from html", function(done){ + xit("import a pad with attributes from html", function(done){ var importurl = helper.padChrome$.window.location.href+'/import' var htmlWithNewLines = 'htmltext
newline' importrequest(htmlWithNewLines,importurl,"html") @@ -88,7 +88,7 @@ describe("import functionality", function(){ expect(results[1][1]).to.be('htmltext\nnewline\n\n') done() }) - it("import a pad with bullets from html", function(done){ + xit("import a pad with bullets from html", function(done){ var importurl = helper.padChrome$.window.location.href+'/import' var htmlWithBullets = '
  • bullet line 1
  • bullet line 2
    • bullet2 line 1
    • bullet2 line 2
' importrequest(htmlWithBullets,importurl,"html") @@ -105,7 +105,7 @@ describe("import functionality", function(){ expect(results[1][1]).to.be('\t* bullet line 1\n\t* bullet line 2\n\t\t* bullet2 line 1\n\t\t* bullet2 line 2\n\n') done() }) - it("import a pad with bullets and newlines from html", function(done){ + xit("import a pad with bullets and newlines from html", function(done){ var importurl = helper.padChrome$.window.location.href+'/import' var htmlWithBullets = '
  • bullet line 1

  • bullet line 2
    • bullet2 line 1

    • bullet2 line 2
' importrequest(htmlWithBullets,importurl,"html") @@ -124,7 +124,7 @@ describe("import functionality", function(){ expect(results[1][1]).to.be('\t* bullet line 1\n\n\t* bullet line 2\n\t\t* bullet2 line 1\n\n\t\t* bullet2 line 2\n\n') done() }) - it("import a pad with bullets and newlines and attributes from html", function(done){ + xit("import a pad with bullets and newlines and attributes from html", function(done){ var importurl = helper.padChrome$.window.location.href+'/import' var htmlWithBullets = '
  • bullet line 1

  • bullet line 2
    • bullet2 line 1

        • bullet4 line 2 bisu
        • bullet4 line 2 bs
        • bullet4 line 2 uuis
' importrequest(htmlWithBullets,importurl,"html") @@ -143,7 +143,7 @@ describe("import functionality", function(){ expect(results[1][1]).to.be('\t* bullet line 1\n\n\t* bullet line 2\n\t\t* bullet2 line 1\n\n\t\t\t\t* bullet4 line 2 bisu\n\t\t\t\t* bullet4 line 2 bs\n\t\t\t\t* bullet4 line 2 uuis\n\n') done() }) - it("import a pad with nested bullets from html", function(done){ + xit("import a pad with nested bullets from html", function(done){ var importurl = helper.padChrome$.window.location.href+'/import' var htmlWithBullets = '
  • bullet line 1
  • bullet line 2
    • bullet2 line 1
        • bullet4 line 2
        • bullet4 line 2
        • bullet4 line 2
      • bullet3 line 1
  • bullet2 line 1
' importrequest(htmlWithBullets,importurl,"html") @@ -165,7 +165,7 @@ describe("import functionality", function(){ expect(results[1][1]).to.be('\t* bullet line 1\n\t* bullet line 2\n\t\t* bullet2 line 1\n\t\t\t\t* bullet4 line 2\n\t\t\t\t* bullet4 line 2\n\t\t\t\t* bullet4 line 2\n\t\t\t* bullet3 line 1\n\t* bullet2 line 1\n\n') done() }) - it("import a pad with 8 levels of bullets and newlines and attributes from html", function(done){ + xit("import a pad with 8 levels of bullets and newlines and attributes from html", function(done){ var importurl = helper.padChrome$.window.location.href+'/import' var htmlWithBullets = '
  • bullet line 1

  • bullet line 2
    • bullet2 line 1

        • bullet4 line 2 bisu
        • bullet4 line 2 bs
        • bullet4 line 2 uuis
                • foo
                • foobar bs
          • foobar
    ' importrequest(htmlWithBullets,importurl,"html") diff --git a/tests/frontend/specs/importindents.js b/tests/frontend/specs/importindents.js index db2b33b0..326d9e97 100644 --- a/tests/frontend/specs/importindents.js +++ b/tests/frontend/specs/importindents.js @@ -49,7 +49,7 @@ describe("import indents functionality", function(){ return exportresults } - it("import a pad with indents from html", function(done){ + xit("import a pad with indents from html", function(done){ var importurl = helper.padChrome$.window.location.href+'/import' var htmlWithIndents = '
    • indent line 1
    • indent line 2
      • indent2 line 1
      • indent2 line 2
    ' importrequest(htmlWithIndents,importurl,"html") @@ -67,7 +67,7 @@ describe("import indents functionality", function(){ done() }) - it("import a pad with indented lists and newlines from html", function(done){ + xit("import a pad with indented lists and newlines from html", function(done){ var importurl = helper.padChrome$.window.location.href+'/import' var htmlWithIndents = '
    • indent line 1

    • indent 1 line 2
      • indent 2 times line 1

      • indent 2 times line 2
    ' importrequest(htmlWithIndents,importurl,"html") @@ -86,7 +86,7 @@ describe("import indents functionality", function(){ expect(results[1][1]).to.be('\tindent line 1\n\n\tindent 1 line 2\n\t\tindent 2 times line 1\n\n\t\tindent 2 times line 2\n\n') done() }) - it("import a pad with 8 levels of indents and newlines and attributes from html", function(done){ + xit("import a pad with 8 levels of indents and newlines and attributes from html", function(done){ var importurl = helper.padChrome$.window.location.href+'/import' var htmlWithIndents = '
    • indent line 1

    • indent line 2
      • indent2 line 1

          • indent4 line 2 bisu
          • indent4 line 2 bs
          • indent4 line 2 uuis
                  • foo
                  • foobar bs
            • foobar
      ' importrequest(htmlWithIndents,importurl,"html") diff --git a/tests/frontend/travis/remote_runner.js b/tests/frontend/travis/remote_runner.js index b94d8c3e..f7b21ed5 100644 --- a/tests/frontend/travis/remote_runner.js +++ b/tests/frontend/travis/remote_runner.js @@ -82,16 +82,18 @@ sauceTestWorker.push({ , 'version' : '' }); +/* // IE 8 sauceTestWorker.push({ 'platform' : 'Windows 2003' , 'browserName' : 'iexplore' , 'version' : '8' }); +*/ // IE 9 sauceTestWorker.push({ - 'platform' : 'Windows 2008' + 'platform' : 'Windows XP' , 'browserName' : 'iexplore' , 'version' : '9' });