diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d06aa871..926e3d3c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 1.5.4 + * SECURITY: Also don't allow read files on directory traversal on frontend tests path + # 1.5.3 * NEW: Accessibility support for Screen readers, includes new fonts and keyboard shortcuts * NEW: API endpoint for Append Chat Message and Chat Backend Tests diff --git a/doc/api/http_api.md b/doc/api/http_api.md index 2ae674d8c..59510a75c 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/src/node/db/API.js b/src/node/db/API.js index edd130e2e..97d5162d8 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/eejs/index.js b/src/node/eejs/index.js index 30f5a442f..9d032840d 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/hooks/express/static.js b/src/node/hooks/express/static.js index 7af54b5d9..34fce29ed 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. diff --git a/src/package.json b/src/package.json index 9778d457c..3d3a25145 100644 --- a/src/package.json +++ b/src/package.json @@ -15,12 +15,12 @@ "etherpad-yajsml" : "0.0.2", "request" : "2.55.0", "requirejs" : "2.1.17", - "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" : "4.12.3", - "express-session" : "1.10.4", + "express-session" : "1.11.1", "cookie-parser" : "1.3.4", "async" : "0.9.0", "clean-css" : "3.1.9", @@ -29,21 +29,21 @@ "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" }, @@ -56,5 +56,5 @@ "repository" : { "type" : "git", "url" : "http://github.com/ether/etherpad-lite.git" }, - "version" : "1.5.3" + "version" : "1.5.4" } diff --git a/src/static/css/iframe_editor.css b/src/static/css/iframe_editor.css index b708e2f4d..b7ece1e66 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 cf062d267..555512118 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -4955,7 +4955,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 eff20b52e..2299bba32 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/tests/backend/specs/api/pad.js b/tests/backend/specs/api/pad.js index 52849c2ea..75e779714 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/importexport.js b/tests/frontend/specs/importexport.js index 59607dba4..2dc002ba0 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