Sanitize pad names

pull/289/head
Jordan 2011-12-16 15:41:11 -05:00
parent ddf1cd345c
commit e8ef99fb72
5 changed files with 145 additions and 70 deletions

View File

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

View File

@ -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<functions[functionName].length;i++)

View File

@ -232,101 +232,108 @@ async.waterfall([
res.send(html);
});
});
//serve pad.html under /p
app.get('/p/:pad', function(req, res, next)
{
//redirects browser to the pad's sanitized url if needed. otherwise, renders the html
function goToPad(req, res, render) {
//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/pad.html");
res.sendfile(filePath, { maxAge: exports.maxAge });
else
{
padManager.sanitizePadId(req.params.pad, function(padId) {
//the pad id was sanitized, so we redirect to the sanitized version
if(padId != req.params.pad)
{
var real_path = req.path.replace(/^\/p\/[^\/]+/, '/p/' + padId);
res.header('Location', real_path);
res.send('You should be redirected to <a href="' + real_path + '">' + real_path + '</a>', 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");

View File

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

View File

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