Sanitize pad names
parent
ddf1cd345c
commit
e8ef99fb72
|
@ -38,6 +38,14 @@ var globalPads = {
|
||||||
remove: function (name) { delete this[':'+name]; }
|
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
|
* Returns a Pad Object with the callback
|
||||||
* @param id A String with the id of the pad
|
* @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)
|
exports.isValidPadId = function(padId)
|
||||||
{
|
{
|
||||||
return /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
|
return /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
var ERR = require("async-stacktrace");
|
var ERR = require("async-stacktrace");
|
||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
var api = require("../db/API");
|
var api = require("../db/API");
|
||||||
|
var padManager = require("../db/PadManager");
|
||||||
|
|
||||||
//ensure we have an apikey
|
//ensure we have an apikey
|
||||||
var apikey = null;
|
var apikey = null;
|
||||||
|
@ -95,7 +96,33 @@ exports.handle = function(functionName, fields, req, res)
|
||||||
res.send({code: 3, message: "no such function", data: null});
|
res.send({code: 3, message: "no such function", data: null});
|
||||||
return;
|
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
|
//put the function parameters in an array
|
||||||
var functionParams = [];
|
var functionParams = [];
|
||||||
for(var i=0;i<functions[functionName].length;i++)
|
for(var i=0;i<functions[functionName].length;i++)
|
||||||
|
|
141
node/server.js
141
node/server.js
|
@ -232,101 +232,108 @@ async.waterfall([
|
||||||
res.send(html);
|
res.send(html);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
//serve pad.html under /p
|
//redirects browser to the pad's sanitized url if needed. otherwise, renders the html
|
||||||
app.get('/p/:pad', function(req, res, next)
|
function goToPad(req, res, render) {
|
||||||
{
|
|
||||||
//ensure the padname is valid and the url doesn't end with a /
|
//ensure the padname is valid and the url doesn't end with a /
|
||||||
if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url))
|
if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url))
|
||||||
{
|
{
|
||||||
res.send('Such a padname is forbidden', 404);
|
res.send('Such a padname is forbidden', 404);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
res.header("Server", serverName);
|
{
|
||||||
var filePath = path.normalize(__dirname + "/../static/pad.html");
|
padManager.sanitizePadId(req.params.pad, function(padId) {
|
||||||
res.sendfile(filePath, { maxAge: exports.maxAge });
|
//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
|
//serve timeslider.html under /p/$padname/timeslider
|
||||||
app.get('/p/:pad/timeslider', function(req, res, next)
|
app.get('/p/:pad/timeslider', function(req, res, next)
|
||||||
{
|
{
|
||||||
//ensure the padname is valid and the url doesn't end with a /
|
goToPad(req, res, function() {
|
||||||
if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url))
|
res.header("Server", serverName);
|
||||||
{
|
var filePath = path.normalize(__dirname + "/../static/timeslider.html");
|
||||||
res.send('Such a padname is forbidden', 404);
|
res.sendfile(filePath, { maxAge: exports.maxAge });
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
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
|
//serve timeslider.html under /p/$padname/timeslider
|
||||||
app.get('/p/:pad/export/:type', function(req, res, next)
|
app.get('/p/:pad/export/:type', function(req, res, next)
|
||||||
{
|
{
|
||||||
//ensure the padname is valid and the url doesn't end with a /
|
goToPad(req, res, function() {
|
||||||
if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url))
|
var types = ["pdf", "doc", "txt", "html", "odt", "dokuwiki"];
|
||||||
{
|
//send a 404 if we don't support this filetype
|
||||||
res.send('Such a padname is forbidden', 404);
|
if(types.indexOf(req.params.type) == -1)
|
||||||
return;
|
{
|
||||||
}
|
next();
|
||||||
|
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)
|
//if abiword is disabled, and this is a format we only support with abiword, output a message
|
||||||
{
|
if(settings.abiword == null &&
|
||||||
next();
|
["odt", "pdf", "doc"].indexOf(req.params.type) !== -1)
|
||||||
return;
|
{
|
||||||
}
|
res.send("Abiword is not enabled at this Etherpad Lite instance. Set the path to Abiword in settings.json to enable this feature");
|
||||||
|
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.header("Access-Control-Allow-Origin", "*");
|
||||||
{
|
res.header("Server", serverName);
|
||||||
res.send("Abiword is not enabled at this Etherpad Lite instance. Set the path to Abiword in settings.json to enable this feature");
|
|
||||||
return;
|
hasPadAccess(req, res, function()
|
||||||
}
|
{
|
||||||
|
exportHandler.doExport(req, res, req.params.pad, req.params.type);
|
||||||
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
|
//handle import requests
|
||||||
app.post('/p/:pad/import', function(req, res, next)
|
app.post('/p/:pad/import', function(req, res, next)
|
||||||
{
|
{
|
||||||
//ensure the padname is valid and the url doesn't end with a /
|
goToPad(req, res, function() {
|
||||||
if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url))
|
//if abiword is disabled, skip handling this request
|
||||||
{
|
if(settings.abiword == null)
|
||||||
res.send('Such a padname is forbidden', 404);
|
{
|
||||||
return;
|
next();
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
//if abiword is disabled, skip handling this request
|
|
||||||
if(settings.abiword == null)
|
|
||||||
{
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.header("Server", serverName);
|
res.header("Server", serverName);
|
||||||
|
|
||||||
hasPadAccess(req, res, function()
|
hasPadAccess(req, res, function()
|
||||||
{
|
{
|
||||||
importHandler.doImport(req, res, req.params.pad);
|
importHandler.doImport(req, res, req.params.pad);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var apiLogger = log4js.getLogger("API");
|
var apiLogger = log4js.getLogger("API");
|
||||||
|
|
||||||
//This is for making an api call, collecting all post information and passing it to the apiHandler
|
//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("Server", serverName);
|
||||||
res.header("Content-Type", "application/json; charset=utf-8");
|
res.header("Content-Type", "application/json; charset=utf-8");
|
||||||
|
|
||||||
|
|
|
@ -194,7 +194,7 @@ function handshake()
|
||||||
padId = decodeURIComponent(padId); // unescape neccesary due to Safari and Opera interpretation of spaces
|
padId = decodeURIComponent(padId); // unescape neccesary due to Safari and Opera interpretation of spaces
|
||||||
|
|
||||||
if(!isReconnect)
|
if(!isReconnect)
|
||||||
document.title = document.title + " | " + padId;
|
document.title = document.title + " | " + padId.replace(/_+/g, ' ');
|
||||||
|
|
||||||
var token = readCookie("token");
|
var token = readCookie("token");
|
||||||
if (token == null)
|
if (token == null)
|
||||||
|
|
|
@ -65,7 +65,7 @@
|
||||||
padId = decodeURIComponent(urlParts[urlParts.length-2]);
|
padId = decodeURIComponent(urlParts[urlParts.length-2]);
|
||||||
|
|
||||||
//set the title
|
//set the title
|
||||||
document.title = document.title + " | " + padId;
|
document.title = document.title + " | " + padId.replace(/_+/g, ' ');
|
||||||
|
|
||||||
//ensure we have a token
|
//ensure we have a token
|
||||||
token = readCookie("token");
|
token = readCookie("token");
|
||||||
|
|
Loading…
Reference in New Issue