lib/index.js
'use strict';
const Caja = require('sanitizer');
const Hoek = require('@hapi/hoek');
const Joi = require('joi');
const internals = {};
internals.whiteRegex = new RegExp(/^[\s\f\n\r\t\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\x09\x0a\x0b\x0c\x0d\x20\xa0]+$/);
internals.noop = (obj) => {
return obj;
};
internals.recurseObj = (item, index, array) => {
if (typeof array[index] === 'object') {
array[index] = internals.sanitize(array[index]);
}
else {
array[index] = Caja.sanitize(array[index]);
}
};
internals.sanitize = (obj) => {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; ++i) {
if (obj[keys[i]] !== null) {
if (typeof obj[keys[i]] === 'object') {
if (Array.isArray(obj[keys[i]])) {
obj[keys[i]].forEach(internals.recurseObj);
}
else {
obj[keys[i]] = internals.sanitize(obj[keys[i]]);
}
}
else if (typeof obj === 'object') {
obj[keys[i]] = Caja.sanitize(obj[keys[i]]);
}
}
}
return obj;
};
internals.deleteEmpty = (obj) => {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; ++i) {
if (obj[keys[i]] === '' || obj[keys[i]] === null) {
delete obj[keys[i]];
}
}
return obj;
};
internals.deleteWhitespace = (obj) => {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; ++i) {
if (internals.whiteRegex.test(obj[keys[i]])) {
delete obj[keys[i]];
}
}
return obj;
};
internals.disinfect = (inputObj, options, firstPass, secondPass) => {
let cleansed = inputObj;
if (cleansed && Object.keys(cleansed).length) {
if (options[firstPass]) {
cleansed = internals.sanitize(cleansed);
}
cleansed = options.genericSanitizer(cleansed);
cleansed = options[secondPass](cleansed);
if (options.deleteWhitespace) {
cleansed = internals.deleteWhitespace(cleansed);
}
if (options.deleteEmpty) {
cleansed = internals.deleteEmpty(cleansed);
}
}
return cleansed;
};
internals.schema = Joi.object().keys({
deleteEmpty: Joi.boolean().optional(),
deleteWhitespace: Joi.boolean().optional(),
disinfectQuery: Joi.boolean().optional(),
disinfectParams: Joi.boolean().optional(),
disinfectPayload: Joi.boolean().optional(),
genericSanitizer: Joi.func().optional(),
querySanitizer: Joi.func().optional(),
paramsSanitizer: Joi.func().optional(),
payloadSanitizer: Joi.func().optional()
});
internals.defaults = {
deleteEmpty: false,
deleteWhitespace: false,
disinfectQuery: false,
disinfectParams: false,
disinfectPayload: false,
genericSanitizer: internals.noop,
querySanitizer: internals.noop,
paramsSanitizer: internals.noop,
payloadSanitizer: internals.noop
};
exports.plugin = {
register: (server, options) => {
const validateOptions = internals.schema.validate(options);
if (validateOptions.error) {
throw validateOptions.error;
}
const serverSettings = Hoek.applyToDefaults(internals.defaults, options);
server.ext('onPostAuth', (request, reply) => {
if (request.route.settings.plugins.disinfect === false) {
return reply.continue;
}
if (request.payload || Object.keys(request.params).length || Object.keys(request.query).length) {
request.route.settings.plugins._disinfect = Hoek.applyToDefaults(serverSettings, request.route.settings.plugins.disinfect || {});
request.query = internals.disinfect(request.query, request.route.settings.plugins._disinfect, 'disinfectQuery', 'querySanitizer');
request.params = internals.disinfect(request.params, request.route.settings.plugins._disinfect, 'disinfectParams', 'paramsSanitizer');
request.payload = internals.disinfect(request.payload, request.route.settings.plugins._disinfect, 'disinfectPayload', 'payloadSanitizer');
}
return reply.continue;
});
},
pkg: require('../package.json')
};