From e8ef99fb728aa792e697e29542ae15b5c06bc774 Mon Sep 17 00:00:00 2001 From: Jordan Date: Fri, 16 Dec 2011 15:41:11 -0500 Subject: [PATCH] Sanitize pad names --- node/db/PadManager.js | 41 +++++++++++ node/handler/APIHandler.js | 29 +++++++- node/server.js | 141 +++++++++++++++++++------------------ static/js/pad2.js | 2 +- static/timeslider.html | 2 +- 5 files changed, 145 insertions(+), 70 deletions(-) diff --git a/node/db/PadManager.js b/node/db/PadManager.js index 36c27263..1aadcd1f 100644 --- a/node/db/PadManager.js +++ b/node/db/PadManager.js @@ -38,6 +38,14 @@ var globalPads = { remove: function (name) { delete this[':'+name]; } }; +/** + * An array of padId transformations. These represent changes in pad name policy over + * time, and allow us to "play back" these changes so legacy padIds can be found. + */ +var padIdTransforms = [ + [/\s+/g, '_'] +]; + /** * Returns a Pad Object with the callback * @param id A String with the id of the pad @@ -110,6 +118,39 @@ exports.doesPadExists = function(padId, callback) }); } +//returns a sanitized padId, respecting legacy pad id formats +exports.sanitizePadId = function(padId, callback) { + var transform_index = arguments[2] || 0; + //we're out of possible transformations, so just return it + if(transform_index >= padIdTransforms.length) + { + callback(padId); + } + //check if padId exists + else + { + exports.doesPadExists(padId, function(junk, exists) + { + if(exists) + { + callback(padId); + } + else + { + //get the next transformation *that's different* + var transformedPadId = padId; + while(transformedPadId == padId && transform_index < padIdTransforms.length) + { + transformedPadId = padId.replace(padIdTransforms[transform_index][0], padIdTransforms[transform_index][1]); + transform_index += 1; + } + //check the next transform + exports.sanitizePadId(transformedPadId, callback, transform_index); + } + }); + } +} + exports.isValidPadId = function(padId) { return /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId); diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index 5c29f9e4..0cd9cb58 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -21,6 +21,7 @@ var ERR = require("async-stacktrace"); var fs = require("fs"); var api = require("../db/API"); +var padManager = require("../db/PadManager"); //ensure we have an apikey var apikey = null; @@ -95,7 +96,33 @@ exports.handle = function(functionName, fields, req, res) res.send({code: 3, message: "no such function", data: null}); return; } - + + //sanitize any pad id's before continuing + if(fields["padID"]) + { + padManager.sanitizePadId(fields["padID"], function(padId) + { + fields["padID"] = padId; + callAPI(functionName, fields, req, res); + }); + } + else if(fields["padName"]) + { + padManager.sanitizePadId(fields["padName"], function(padId) + { + fields["padName"] = padId; + callAPI(functionName, fields, req, res); + }); + } + else + { + callAPI(functionName, fields, req, res); + } +} + +//calls the api function +function callAPI(functionName, fields, req, res) +{ //put the function parameters in an array var functionParams = []; for(var i=0;i' + real_path + '', 302); + } + //the pad id was fine, so just render it + else + { + render(); + } + }); + } + } + + //serve pad.html under /p + app.get('/p/:pad', function(req, res, next) + { + goToPad(req, res, function() { + res.header("Server", serverName); + var filePath = path.normalize(__dirname + "/../static/pad.html"); + res.sendfile(filePath, { maxAge: exports.maxAge }); + }); }); //serve timeslider.html under /p/$padname/timeslider app.get('/p/:pad/timeslider', function(req, res, next) { - //ensure the padname is valid and the url doesn't end with a / - if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url)) - { - res.send('Such a padname is forbidden', 404); - return; - } - - res.header("Server", serverName); - var filePath = path.normalize(__dirname + "/../static/timeslider.html"); - res.sendfile(filePath, { maxAge: exports.maxAge }); + goToPad(req, res, function() { + res.header("Server", serverName); + var filePath = path.normalize(__dirname + "/../static/timeslider.html"); + res.sendfile(filePath, { maxAge: exports.maxAge }); + }); }); //serve timeslider.html under /p/$padname/timeslider app.get('/p/:pad/export/:type', function(req, res, next) { - //ensure the padname is valid and the url doesn't end with a / - if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url)) - { - res.send('Such a padname is forbidden', 404); - return; - } - - var types = ["pdf", "doc", "txt", "html", "odt", "dokuwiki"]; - //send a 404 if we don't support this filetype - if(types.indexOf(req.params.type) == -1) - { - next(); - return; - } - - //if abiword is disabled, and this is a format we only support with abiword, output a message - if(settings.abiword == null && - ["odt", "pdf", "doc"].indexOf(req.params.type) !== -1) - { - res.send("Abiword is not enabled at this Etherpad Lite instance. Set the path to Abiword in settings.json to enable this feature"); - return; - } - - res.header("Access-Control-Allow-Origin", "*"); - res.header("Server", serverName); - - hasPadAccess(req, res, function() - { - exportHandler.doExport(req, res, req.params.pad, req.params.type); + goToPad(req, res, function() { + var types = ["pdf", "doc", "txt", "html", "odt", "dokuwiki"]; + //send a 404 if we don't support this filetype + if(types.indexOf(req.params.type) == -1) + { + next(); + return; + } + + //if abiword is disabled, and this is a format we only support with abiword, output a message + if(settings.abiword == null && + ["odt", "pdf", "doc"].indexOf(req.params.type) !== -1) + { + res.send("Abiword is not enabled at this Etherpad Lite instance. Set the path to Abiword in settings.json to enable this feature"); + return; + } + + res.header("Access-Control-Allow-Origin", "*"); + res.header("Server", serverName); + + hasPadAccess(req, res, function() + { + exportHandler.doExport(req, res, req.params.pad, req.params.type); + }); }); }); //handle import requests app.post('/p/:pad/import', function(req, res, next) { - //ensure the padname is valid and the url doesn't end with a / - if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url)) - { - res.send('Such a padname is forbidden', 404); - return; - } - - //if abiword is disabled, skip handling this request - if(settings.abiword == null) - { - next(); - return; - } + goToPad(req, res, function() { + //if abiword is disabled, skip handling this request + if(settings.abiword == null) + { + next(); + return; + } - res.header("Server", serverName); - - hasPadAccess(req, res, function() - { - importHandler.doImport(req, res, req.params.pad); + res.header("Server", serverName); + + hasPadAccess(req, res, function() + { + importHandler.doImport(req, res, req.params.pad); + }); }); }); var apiLogger = log4js.getLogger("API"); //This is for making an api call, collecting all post information and passing it to the apiHandler - var apiCaller = function(req, res, fields) { + var apiCaller = function(req, res, fields) + { res.header("Server", serverName); res.header("Content-Type", "application/json; charset=utf-8"); diff --git a/static/js/pad2.js b/static/js/pad2.js index b75c1f9c..bbb385b3 100644 --- a/static/js/pad2.js +++ b/static/js/pad2.js @@ -194,7 +194,7 @@ function handshake() padId = decodeURIComponent(padId); // unescape neccesary due to Safari and Opera interpretation of spaces if(!isReconnect) - document.title = document.title + " | " + padId; + document.title = document.title + " | " + padId.replace(/_+/g, ' '); var token = readCookie("token"); if (token == null) diff --git a/static/timeslider.html b/static/timeslider.html index 11c5ef7f..4ca11f3a 100644 --- a/static/timeslider.html +++ b/static/timeslider.html @@ -65,7 +65,7 @@ padId = decodeURIComponent(urlParts[urlParts.length-2]); //set the title - document.title = document.title + " | " + padId; + document.title = document.title + " | " + padId.replace(/_+/g, ' '); //ensure we have a token token = readCookie("token");