// Disable no-var because we need to support old IE for now. /* eslint-disable no-var */ // Disable space-before-function-paren for compatibility with VS Code default JS formatter. /* eslint-disable space-before-function-paren */ /* * This module is intended to be executed both on client side and server side. * No error should be thrown. (soft error handling) */ (function () { var root = {}; // Dependencies -------------------------------------------------------------- root.async = (typeof require === 'function') ? require('async') : window.async; if (typeof root.async !== 'object') { throw new Error('Module async is required (https://github.com/caolan/async)'); } var async = root.async; function _extend(origin, add) { if (!add || typeof add !== 'object') { return origin; } var keys = Object.keys(add); var i = keys.length; while (i--) { origin[keys[i]] = add[keys[i]]; } return origin; } function _merge() { var ret = {}; var args = Array.prototype.slice.call(arguments); var keys = null; var i = null; args.forEach(function (arg) { if (arg && arg.constructor === Object) { keys = Object.keys(arg); i = keys.length; while (i--) { ret[keys[i]] = arg[keys[i]]; } } }); return ret; } // Customisable class (Base class) ------------------------------------------- // Use with operation "new" to extend Validation and Sanitization themselves, // not their prototype. In other words, constructor shall be call to extend // those functions, instead of being in their constructor, like this: // _extend(Validation, new Customisable); function Customisable() { this.custom = {}; this.extend = function (custom) { return _extend(this.custom, custom); }; this.reset = function () { this.custom = {}; }; this.remove = function (fields) { if (!_typeIs.array(fields)) { fields = [fields]; } fields.forEach(function (field) { delete this.custom[field]; }, this); }; } // Inspection class (Base class) --------------------------------------------- // Use to extend Validation and Sanitization prototypes. Inspection // constructor shall be called in derived class constructor. function Inspection(schema, custom) { var _stack = ['@']; this._schema = schema; this._custom = {}; if (custom != null) { for (var key in custom) { if (Object.prototype.hasOwnProperty.call(custom, key)) { this._custom['$' + key] = custom[key]; } } } this._getDepth = function () { return _stack.length; }; this._dumpStack = function () { /* eslint-disable no-control-regex */ return _stack.map(function (i) { return i.replace(/^\[/g, '\u001b\u001c\u001d\u001e'); }) .join('.').replace(/\.\u001b\u001c\u001d\u001e/g, '['); /* eslint-enable no-control-regex */ }; this._deeperObject = function (name) { _stack.push((/^[a-z$_][a-z0-9$_]*$/i).test(name) ? name : '["' + name + '"]'); return this; }; this._deeperArray = function (i) { _stack.push('[' + i + ']'); return this; }; this._back = function () { _stack.pop(); return this; }; } // Simple types -------------------------------------------------------------- // If the property is not defined or is not in this list: var _typeIs = { function: function (element) { return typeof element === 'function'; }, string: function (element) { return typeof element === 'string'; }, number: function (element) { return typeof element === 'number' && !isNaN(element); }, integer: function (element) { return typeof element === 'number' && element % 1 === 0; }, NaN: function (element) { return typeof element === 'number' && isNaN(element); }, boolean: function (element) { return typeof element === 'boolean'; }, null: function (element) { return element === null; }, date: function (element) { return element != null && element instanceof Date; }, object: function (element) { return typeof element === 'object' && element != null && element.constructor !== Array; }, array: function (element) { return element != null && element.constructor === Array; }, any: function () { return true; } }; function _simpleType(type, candidate) { if (typeof type === 'function') { return candidate instanceof type; } type = type in _typeIs ? type : 'any'; return _typeIs[type](candidate); } function _realType(candidate) { for (var i in _typeIs) { if (_simpleType(i, candidate)) { if (i !== 'any' && (i !== 'object' || candidate.constructor === Object)) { return i; } return 'an instance of ' + candidate.constructor.name; } } } function getIndexes(a, value) { var indexes = []; var i = a.indexOf(value); while (i !== -1) { indexes.push(i); i = a.indexOf(value, i + 1); } return indexes; } // Available formats --------------------------------------------------------- /* eslint-disable no-useless-escape */ // TODO: Study these regex expressions and add more tests so we can consider removing // rule no-useless-escape. var _formats = { void: /^$/, url: /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)?(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i, 'date-time': /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z?|(-|\+)\d{2}:\d{2})$/, date: /^\d{4}-\d{2}-\d{2}$/, coolDateTime: /^\d{4}(-|\/)\d{2}(-|\/)\d{2}(T| )\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/, time: /^\d{2}\:\d{2}\:\d{2}$/, color: /^#([0-9a-f])+$/i, // Very flexible regular expression designed to catch only obvious mistakes // that a user might make that would have a 100% chance of causing email // delivery to the address to fail. The library takes a flexible over // strict approach. Users should use this only for basic front end email // address validation and perform more strict checking using server-side // code after the data has been sent to their server. // Sourced from https://www.regular-expressions.info/email.html, modified // to allow lowercase characters too. email: /^[^@]+@[^.]+\.[a-zA-Z]+$/, /* eslint-enable prefer-regex-literals */ /* eslint-enable quotes */ numeric: /^[0-9]+$/, integer: /^\-?[0-9]+$/, decimal: /^\-?[0-9]*\.?[0-9]+$/, alpha: /^[a-z]+$/i, alphaNumeric: /^[a-z0-9]+$/i, alphaDash: /^[a-z0-9_-]+$/i, javascript: /^[a-z_\$][a-z0-9_\$]*$/i, upperString: /^[A-Z ]*$/, lowerString: /^[a-z ]*$/, v4uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$/i }; /* eslint-enable no-useless-escape */ // Validation ------------------------------------------------------------------ var _validationAttribut = { optional: function (schema, candidate) { var opt = typeof schema.optional === 'boolean' ? schema.optional : (schema.optional === 'true'); // Default is false if (opt === true) { return; } if (typeof candidate === 'undefined') { this.report('is missing and not optional', null, 'optional'); } }, type: function (schema, candidate) { // return because optional function already handle this case if (typeof candidate === 'undefined' || (typeof schema.type !== 'string' && !(schema.type instanceof Array) && typeof schema.type !== 'function')) { return; } var types = _typeIs.array(schema.type) ? schema.type : [schema.type]; var typeIsValid = types.some(function (type) { return _simpleType(type, candidate); }); if (!typeIsValid) { types = types.map(function (t) { return typeof t === 'function' ? 'an instance of ' + t.name : t; }); this.report('must be ' + types.join(' or ') + ', but is ' + _realType(candidate), null, 'type'); } }, uniqueness: function (schema, candidate) { if (typeof schema.uniqueness === 'string') { schema.uniqueness = (schema.uniqueness === 'true'); } if (typeof schema.uniqueness !== 'boolean' || schema.uniqueness === false || (!_typeIs.array(candidate) && typeof candidate !== 'string')) { return; } var reported = []; for (var i = 0; i < candidate.length; i++) { if (reported.indexOf(candidate[i]) >= 0) { continue; } var indexes = getIndexes(candidate, candidate[i]); if (indexes.length > 1) { reported.push(candidate[i]); this.report('has value [' + candidate[i] + '] more than once at indexes [' + indexes.join(', ') + ']', null, 'uniqueness'); } } }, pattern: function (schema, candidate) { var self = this; var regexs = schema.pattern; if (typeof candidate !== 'string') { return; } var matches = false; if (!_typeIs.array(regexs)) { regexs = [regexs]; } regexs.forEach(function (regex) { if (typeof regex === 'string' && regex in _formats) { regex = _formats[regex]; } if (regex instanceof RegExp) { if (regex.test(candidate)) { matches = true; } } }); if (!matches) { self.report('must match [' + regexs.join(' or ') + '], but is equal to "' + candidate + '"', null, 'pattern'); } }, validDate: function (schema, candidate) { if (String(schema.validDate) === 'true' && candidate instanceof Date && isNaN(candidate.getTime())) { this.report('must be a valid date', null, 'validDate'); } }, minLength: function (schema, candidate) { if (typeof candidate !== 'string' && !_typeIs.array(candidate)) { return; } var minLength = Number(schema.minLength); if (isNaN(minLength)) { return; } if (candidate.length < minLength) { this.report('must be longer than ' + minLength + ' elements, but it has ' + candidate.length, null, 'minLength'); } }, maxLength: function (schema, candidate) { if (typeof candidate !== 'string' && !_typeIs.array(candidate)) { return; } var maxLength = Number(schema.maxLength); if (isNaN(maxLength)) { return; } if (candidate.length > maxLength) { this.report('must be shorter than ' + maxLength + ' elements, but it has ' + candidate.length, null, 'maxLength'); } }, exactLength: function (schema, candidate) { if (typeof candidate !== 'string' && !_typeIs.array(candidate)) { return; } var exactLength = Number(schema.exactLength); if (isNaN(exactLength)) { return; } if (candidate.length !== exactLength) { this.report('must have exactly ' + exactLength + ' elements, but it have ' + candidate.length, null, 'exactLength'); } }, lt: function (schema, candidate) { var limit = Number(schema.lt); if (typeof candidate !== 'number' || isNaN(limit)) { return; } if (candidate >= limit) { this.report('must be less than ' + limit + ', but is equal to "' + candidate + '"', null, 'lt'); } }, lte: function (schema, candidate) { var limit = Number(schema.lte); if (typeof candidate !== 'number' || isNaN(limit)) { return; } if (candidate > limit) { this.report('must be less than or equal to ' + limit + ', but is equal to "' + candidate + '"', null, 'lte'); } }, gt: function (schema, candidate) { var limit = Number(schema.gt); if (typeof candidate !== 'number' || isNaN(limit)) { return; } if (candidate <= limit) { this.report('must be greater than ' + limit + ', but is equal to "' + candidate + '"', null, 'gt'); } }, gte: function (schema, candidate) { var limit = Number(schema.gte); if (typeof candidate !== 'number' || isNaN(limit)) { return; } if (candidate < limit) { this.report('must be greater than or equal to ' + limit + ', but is equal to "' + candidate + '"', null, 'gte'); } }, eq: function (schema, candidate) { if (typeof candidate !== 'number' && typeof candidate !== 'string' && typeof candidate !== 'boolean') { return; } var limit = schema.eq; if (typeof limit !== 'number' && typeof limit !== 'string' && typeof limit !== 'boolean' && !_typeIs.array(limit)) { return; } if (_typeIs.array(limit)) { for (var i = 0; i < limit.length; i++) { if (candidate === limit[i]) { return; } } this.report('must be equal to [' + limit.map(function (l) { return '"' + l + '"'; }).join(' or ') + '], but is equal to "' + candidate + '"', null, 'eq'); } else { if (candidate !== limit) { this.report('must be equal to "' + limit + '", but is equal to "' + candidate + '"', null, 'eq'); } } }, ne: function (schema, candidate) { if (typeof candidate !== 'number' && typeof candidate !== 'string') { return; } var limit = schema.ne; if (typeof limit !== 'number' && typeof limit !== 'string' && !_typeIs.array(limit)) { return; } if (_typeIs.array(limit)) { for (var i = 0; i < limit.length; i++) { if (candidate === limit[i]) { this.report('must not be equal to "' + limit[i] + '"', null, 'ne'); return; } } } else { if (candidate === limit) { this.report('must not be equal to "' + limit + '"', null, 'ne'); } } }, multipleOf: function (schema, candidate) { const divisor = Number(schema.multipleOf); if (typeof candidate !== 'number' || isNaN(divisor)) { return; } if (candidate % divisor !== 0) { this.report(candidate + ' is not divisible by ' + divisor, null, 'multipleOf'); } }, someKeys: function (schema, candidat) { var _keys = schema.someKeys; if (!_typeIs.object(candidat)) { return; } var valid = _keys.some(function (action) { return (action in candidat); }); if (!valid) { this.report('must have at least key ' + _keys.map(function (i) { return '"' + i + '"'; }).join(' or '), null, 'someKeys'); } }, strict: function (schema, candidate) { if (typeof schema.strict === 'string') { schema.strict = (schema.strict === 'true'); } if (schema.strict !== true || !_typeIs.object(candidate) || !_typeIs.object(schema.properties)) { return; } var self = this; if (typeof schema.properties['*'] === 'undefined') { var intruder = Object.keys(candidate).filter(function (key) { return (typeof schema.properties[key] === 'undefined'); }); if (intruder.length > 0) { var msg = 'should not contains ' + (intruder.length > 1 ? 'properties' : 'property') + ' [' + intruder.map(function (i) { return '"' + i + '"'; }).join(', ') + ']'; self.report(msg, null, 'strict'); } } }, exec: function (schema, candidate, callback) { var self = this; if (typeof callback === 'function') { return this.asyncExec(schema, candidate, callback); } (_typeIs.array(schema.exec) ? schema.exec : [schema.exec]).forEach(function (exec) { if (typeof exec === 'function') { exec.call(self, schema, candidate); } }); }, properties: function (schema, candidate, callback) { if (typeof callback === 'function') { return this.asyncProperties(schema, candidate, callback); } if (!(schema.properties instanceof Object) || !(candidate instanceof Object)) { return; } var properties = schema.properties; var i; if (properties['*'] != null) { for (i in candidate) { if (i in properties) { continue; } this._deeperObject(i); this._validate(properties['*'], candidate[i]); this._back(); } } for (i in properties) { if (i === '*') { continue; } this._deeperObject(i); this._validate(properties[i], candidate[i]); this._back(); } }, items: function (schema, candidate, callback) { if (typeof callback === 'function') { return this.asyncItems(schema, candidate, callback); } if (!(schema.items instanceof Object) || !(candidate instanceof Object)) { return; } var items = schema.items; var i, l; // If provided schema is an array // then call validate for each case // else it is an Object // then call validate for each key if (_typeIs.array(items) && _typeIs.array(candidate)) { for (i = 0, l = items.length; i < l; i++) { this._deeperArray(i); this._validate(items[i], candidate[i]); this._back(); } } else { for (var key in candidate) { if (Object.prototype.hasOwnProperty.call(candidate, key)) { this._deeperArray(key); this._validate(items, candidate[key]); this._back(); } } } } }; var _asyncValidationAttribut = { asyncExec: function (schema, candidate, callback) { var self = this; async.eachSeries(_typeIs.array(schema.exec) ? schema.exec : [schema.exec], function (exec, done) { if (typeof exec === 'function') { if (exec.length > 2) { return exec.call(self, schema, candidate, done); } exec.call(self, schema, candidate); } async.nextTick(done); }, callback); }, asyncProperties: function (schema, candidate, callback) { if (!(schema.properties instanceof Object) || !_typeIs.object(candidate)) { return callback(); } var self = this; var properties = schema.properties; async.series([ function (next) { if (properties['*'] == null) { return next(); } async.eachSeries(Object.keys(candidate), function (i, done) { if (i in properties) { return async.nextTick(done); } self._deeperObject(i); self._asyncValidate(properties['*'], candidate[i], function (err) { self._back(); done(err); }); }, next); }, function (next) { async.eachSeries(Object.keys(properties), function (i, done) { if (i === '*') { return async.nextTick(done); } self._deeperObject(i); self._asyncValidate(properties[i], candidate[i], function (err) { self._back(); done(err); }); }, next); } ], callback); }, asyncItems: function (schema, candidate, callback) { if (!(schema.items instanceof Object) || !(candidate instanceof Object)) { return callback(); } var self = this; var items = schema.items; if (_typeIs.array(items) && _typeIs.array(candidate)) { async.timesSeries(items.length, function (i, done) { self._deeperArray(i); self._asyncValidate(items[i], candidate[i], function (err, res) { self._back(); done(err, res); }); self._back(); }, callback); } else { async.eachSeries(Object.keys(candidate), function (key, done) { self._deeperArray(key); self._asyncValidate(items, candidate[key], function (err, res) { self._back(); done(err, res); }); }, callback); } } }; // Validation Class ---------------------------------------------------------- // inherits from Inspection class (actually we just call Inspection // constructor with the new context, because its prototype is empty function Validation(schema, custom) { Inspection.prototype.constructor.call(this, schema, _merge(Validation.custom, custom)); var _error = []; this._basicFields = Object.keys(_validationAttribut); this._customFields = Object.keys(this._custom); this.origin = null; this.report = function (message, code, reason) { var newErr = { code: code || this.userCode || null, reason: reason || 'unknown', message: this.userError || message || 'is invalid', property: this.userAlias ? (this.userAlias + ' (' + this._dumpStack() + ')') : this._dumpStack() }; _error.push(newErr); return this; }; this.result = function () { return { error: _error, valid: _error.length === 0, format: function () { if (this.valid === true) { return 'Candidate is valid'; } return this.error.map(function (i) { return 'Property ' + i.property + ': ' + i.message; }).join('\n'); } }; }; } _extend(Validation.prototype, _validationAttribut); _extend(Validation.prototype, _asyncValidationAttribut); _extend(Validation, new Customisable()); Validation.prototype.validate = function (candidate, callback) { this.origin = candidate; if (typeof callback === 'function') { var self = this; return async.nextTick(function () { self._asyncValidate(self._schema, candidate, function (err) { self.origin = null; callback(err, self.result()); }); }); } return this._validate(this._schema, candidate).result(); }; Validation.prototype._validate = function (schema, candidate, callback) { this.userCode = schema.code || null; this.userError = schema.error || null; this.userAlias = schema.alias || null; this._basicFields.forEach(function (i) { if ((i in schema || i === 'optional') && typeof this[i] === 'function') { this[i](schema, candidate); } }, this); this._customFields.forEach(function (i) { if (i in schema && typeof this._custom[i] === 'function') { this._custom[i].call(this, schema, candidate); } }, this); return this; }; Validation.prototype._asyncValidate = function (schema, candidate, callback) { var self = this; this.userCode = schema.code || null; this.userError = schema.error || null; this.userAlias = schema.alias || null; async.series([ function (next) { async.eachSeries(Object.keys(_validationAttribut), function (i, done) { async.nextTick(function () { if ((i in schema || i === 'optional') && typeof self[i] === 'function') { if (self[i].length > 2) { return self[i](schema, candidate, done); } self[i](schema, candidate); } done(); }); }, next); }, function (next) { async.eachSeries(Object.keys(self._custom), function (i, done) { async.nextTick(function () { if (i in schema && typeof self._custom[i] === 'function') { if (self._custom[i].length > 2) { return self._custom[i].call(self, schema, candidate, done); } self._custom[i].call(self, schema, candidate); } done(); }); }, next); } ], callback); }; // Sanitization ---------------------------------------------------------------- // functions called by _sanitization.type method. var _forceType = { number: function (post, schema) { var n; if (typeof post === 'number') { return post; } else if (post === '') { if (typeof schema.def !== 'undefined') { return schema.def; } return null; } else if (typeof post === 'string') { n = parseFloat(post.replace(/,/g, '.').replace(/ /g, '')); if (typeof n === 'number') { return n; } } else if (post instanceof Date) { return +post; } return null; }, integer: function (post, schema) { var n; if (typeof post === 'number' && post % 1 === 0) { return post; } else if (post === '') { if (typeof schema.def !== 'undefined') { return schema.def; } return null; } else if (typeof post === 'string') { n = parseInt(post.replace(/ /g, ''), 10); if (typeof n === 'number') { return n; } } else if (typeof post === 'number') { return parseInt(post, 10); } else if (typeof post === 'boolean') { if (post) { return 1; } return 0; } else if (post instanceof Date) { return +post; } return null; }, string: function (post, schema) { if (typeof post === 'boolean' || typeof post === 'number' || post instanceof Date) { return post.toString(); } else if (_typeIs.array(post)) { // If user authorize array and strings... if (schema.items || schema.properties) { return post; } return post.join(String(schema.joinWith || ',')); } else if (post instanceof Object) { // If user authorize objects ans strings... if (schema.items || schema.properties) { return post; } return JSON.stringify(post); } else if (typeof post === 'string' && post.length) { return post; } return null; }, date: function (post, schema) { if (post instanceof Date) { return post; } else { var d = new Date(post); if (!isNaN(d.getTime())) { // if valid date return d; } } return null; }, boolean: function (post, schema) { if (typeof post === 'undefined') return null; if (typeof post === 'string' && post.toLowerCase() === 'false') return false; return !!post; }, object: function (post, schema) { if (typeof post !== 'string' || _typeIs.object(post)) { return post; } try { return JSON.parse(post); } catch (e) { return null; } }, array: function (post, schema) { if (_typeIs.array(post)) { return post; } if (typeof post === 'undefined') { return null; } if (typeof post === 'string') { if (post.substring(0, 1) === '[' && post.slice(-1) === ']') { try { return JSON.parse(post); } catch (e) { return null; } } return post.split(String(schema.splitWith || ',')); } if (!_typeIs.array(post)) { return [post]; } return null; } }; var _applyRules = { upper: function (post) { return post.toUpperCase(); }, lower: function (post) { return post.toLowerCase(); }, title: function (post) { // Fix by seb (replace \w\S* by \S* => exemple : coucou ça va) return post.replace(/\S*/g, function (txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); }); }, capitalize: function (post) { return post.charAt(0).toUpperCase() + post.substr(1).toLowerCase(); }, ucfirst: function (post) { return post.charAt(0).toUpperCase() + post.substr(1); }, trim: function (post) { return post.trim(); } }; // Every function return the future value of each property. Therefore you // have to return post even if you do not change its value var _sanitizationAttribut = { strict: function (schema, post) { if (typeof schema.strict === 'string') { schema.strict = (schema.strict === 'true'); } if (schema.strict !== true) { return post; } if (!_typeIs.object(schema.properties)) { return post; } if (!_typeIs.object(post)) { return post; } Object.keys(post).forEach(function (key) { if (!(key in schema.properties)) { delete post[key]; } }); return post; }, optional: function (schema, post) { var opt = typeof schema.optional === 'boolean' ? schema.optional : (schema.optional !== 'false'); // Default: true if (opt === true) { return post; } if (typeof post !== 'undefined') { return post; } this.report(); if (schema.def === Date) { return new Date(); } return schema.def; }, type: function (schema, post) { // if (_typeIs['object'](post) || _typeIs.array(post)) { // return post; // } if (typeof schema.type !== 'string' || typeof _forceType[schema.type] !== 'function') { return post; } var n; var opt = typeof schema.optional === 'boolean' ? schema.optional : true; if (typeof _forceType[schema.type] === 'function') { n = _forceType[schema.type](post, schema); if ((n === null && !opt) || (!n && isNaN(n)) || (n === null && schema.type === 'string')) { n = schema.def; } } else if (!opt) { n = schema.def; } if ((n != null || (typeof schema.def !== 'undefined' && schema.def === n)) && n !== post) { this.report(); return n; } return post; }, rules: function (schema, post) { var rules = schema.rules; if (typeof post !== 'string' || (typeof rules !== 'string' && !_typeIs.array(rules))) { return post; } var modified = false; (_typeIs.array(rules) ? rules : [rules]).forEach(function (rule) { if (typeof _applyRules[rule] === 'function') { post = _applyRules[rule](post); modified = true; } }); if (modified) { this.report(); } return post; }, min: function (schema, post) { var postTest = Number(post); if (isNaN(postTest)) { return post; } var min = Number(schema.min); if (isNaN(min)) { return post; } if (postTest < min) { this.report(); return min; } return post; }, max: function (schema, post) { var postTest = Number(post); if (isNaN(postTest)) { return post; } var max = Number(schema.max); if (isNaN(max)) { return post; } if (postTest > max) { this.report(); return max; } return post; }, minLength: function (schema, post) { var limit = Number(schema.minLength); if (typeof post !== 'string' || isNaN(limit) || limit < 0) { return post; } var str = ''; var gap = limit - post.length; if (gap > 0) { for (var i = 0; i < gap; i++) { str += '-'; } this.report(); return post + str; } return post; }, maxLength: function (schema, post) { var limit = Number(schema.maxLength); if (typeof post !== 'string' || isNaN(limit) || limit < 0) { return post; } if (post.length > limit) { this.report(); return post.slice(0, limit); } return post; }, properties: function (schema, post, callback) { if (typeof callback === 'function') { return this.asyncProperties(schema, post, callback); } if (!post || typeof post !== 'object') { return post; } var properties = schema.properties; var tmp; var i; if (typeof properties['*'] !== 'undefined') { for (i in post) { if (i in properties) { continue; } this._deeperObject(i); tmp = this._sanitize(properties['*'], post[i]); if (typeof tmp !== 'undefined' || 'exec' in properties['*']) { post[i] = tmp; } this._back(); } } for (i in properties) { if (i !== '*') { this._deeperObject(i); tmp = this._sanitize(properties[i], post[i]); if (typeof tmp !== 'undefined' || 'exec' in properties[i]) { post[i] = tmp; } this._back(); } } return post; }, items: function (schema, post, callback) { if (typeof callback === 'function') { return this.asyncItems(schema, post, callback); } if (!(schema.items instanceof Object) || !(post instanceof Object)) { return post; } var i; if (_typeIs.array(schema.items) && _typeIs.array(post)) { var minLength = schema.items.length < post.length ? schema.items.length : post.length; for (i = 0; i < minLength; i++) { this._deeperArray(i); post[i] = this._sanitize(schema.items[i], post[i]); this._back(); } } else { for (i in post) { if (Object.prototype.hasOwnProperty.call(post, i)) { this._deeperArray(i); post[i] = this._sanitize(schema.items, post[i]); this._back(); } } } return post; }, exec: function (schema, post, callback) { if (typeof callback === 'function') { return this.asyncExec(schema, post, callback); } var execs = _typeIs.array(schema.exec) ? schema.exec : [schema.exec]; execs.forEach(function (exec) { if (typeof exec === 'function') { post = exec.call(this, schema, post); } }, this); return post; } }; var _asyncSanitizationAttribut = { asyncExec: function (schema, post, callback) { var self = this; var execs = _typeIs.array(schema.exec) ? schema.exec : [schema.exec]; async.eachSeries(execs, function (exec, done) { if (typeof exec === 'function') { if (exec.length > 2) { return exec.call(self, schema, post, function (err, res) { if (err) { return done(err); } post = res; done(); }); } post = exec.call(self, schema, post); } done(); }, function (err) { callback(err, post); }); }, asyncProperties: function (schema, post, callback) { if (!post || typeof post !== 'object') { return callback(null, post); } var self = this; var properties = schema.properties; async.series([ function (next) { if (properties['*'] == null) { return next(); } var globing = properties['*']; async.eachSeries(Object.keys(post), function (i, next) { if (i in properties) { return next(); } self._deeperObject(i); self._asyncSanitize(globing, post[i], function (err, res) { if (err) { /* Error can safely be ignored here */ } if (typeof res !== 'undefined') { post[i] = res; } self._back(); next(); }); }, next); }, function (next) { async.eachSeries(Object.keys(properties), function (i, next) { if (i === '*') { return next(); } self._deeperObject(i); self._asyncSanitize(properties[i], post[i], function (err, res) { if (err) { return next(err); } if (typeof res !== 'undefined') { post[i] = res; } self._back(); next(); }); }, next); } ], function (err) { return callback(err, post); }); }, asyncItems: function (schema, post, callback) { if (!(schema.items instanceof Object) || !(post instanceof Object)) { return callback(null, post); } var self = this; var items = schema.items; if (_typeIs.array(items) && _typeIs.array(post)) { var minLength = items.length < post.length ? items.length : post.length; async.timesSeries(minLength, function (i, next) { self._deeperArray(i); self._asyncSanitize(items[i], post[i], function (err, res) { if (err) { return next(err); } post[i] = res; self._back(); next(); }); }, function (err) { callback(err, post); }); } else { async.eachSeries(Object.keys(post), function (key, next) { self._deeperArray(key); self._asyncSanitize(items, post[key], function (err, res) { if (err) { return next(); } post[key] = res; self._back(); next(); }); }, function (err) { callback(err, post); }); } return post; } }; // Sanitization Class -------------------------------------------------------- // inherits from Inspection class (actually we just call Inspection // constructor with the new context, because its prototype is empty function Sanitization(schema, custom) { Inspection.prototype.constructor.call(this, schema, _merge(Sanitization.custom, custom)); var _reporting = []; this._basicFields = Object.keys(_sanitizationAttribut); this._customFields = Object.keys(this._custom); this.origin = null; this.report = function (message) { var newNot = { message: message || 'was sanitized', property: this.userAlias ? (this.userAlias + ' (' + this._dumpStack() + ')') : this._dumpStack() }; if (!_reporting.some(function (e) { return e.property === newNot.property; })) { _reporting.push(newNot); } }; this.result = function (data) { // For old IE. /* eslint-disable object-shorthand */ return { data: data, reporting: _reporting, format: function () { return this.reporting.map(function (i) { return 'Property ' + i.property + ' ' + i.message; }).join('\n'); } }; /* eslint-enable object-shorthand */ }; } _extend(Sanitization.prototype, _sanitizationAttribut); _extend(Sanitization.prototype, _asyncSanitizationAttribut); _extend(Sanitization, new Customisable()); Sanitization.prototype.sanitize = function (post, callback) { this.origin = post; if (typeof callback === 'function') { var self = this; return this._asyncSanitize(this._schema, post, function (err, data) { self.origin = null; callback(err, self.result(data)); }); } var data = this._sanitize(this._schema, post); this.origin = null; return this.result(data); }; Sanitization.prototype._sanitize = function (schema, post) { this.userAlias = schema.alias || null; this._basicFields.forEach(function (i) { if ((i in schema || i === 'optional') && typeof this[i] === 'function') { post = this[i](schema, post); } }, this); this._customFields.forEach(function (i) { if (i in schema && typeof this._custom[i] === 'function') { post = this._custom[i].call(this, schema, post); } }, this); return post; }; Sanitization.prototype._asyncSanitize = function (schema, post, callback) { var self = this; this.userAlias = schema.alias || null; async.waterfall([ function (next) { async.reduce(self._basicFields, post, function (value, i, next) { async.nextTick(function () { if ((i in schema || i === 'optional') && typeof self[i] === 'function') { if (self[i].length > 2) { return self[i](schema, value, next); } value = self[i](schema, value); } next(null, value); }); }, next); }, function (inter, next) { async.reduce(self._customFields, inter, function (value, i, next) { async.nextTick(function () { if (i in schema && typeof self._custom[i] === 'function') { if (self._custom[i].length > 2) { return self._custom[i].call(self, schema, value, next); } value = self._custom[i].call(self, schema, value); } next(null, value); }); }, next); } ], callback); }; // --------------------------------------------------------------------------- var INT_MIN = -2147483648; var INT_MAX = 2147483647; var _rand = { int: function (min, max) { return min + (0 | Math.random() * (max - min + 1)); }, float: function (min, max) { return (Math.random() * (max - min) + min); }, bool: function () { return (Math.random() > 0.5); }, char: function (min, max) { return String.fromCharCode(this.int(min, max)); }, fromList: function (list) { return list[this.int(0, list.length - 1)]; } }; var _formatSample = { 'date-time': function () { return new Date().toISOString(); }, date: function () { return new Date().toISOString().replace(/T.*$/, ''); }, time: function () { return new Date().toLocaleTimeString({}, { hour12: false }); }, color: function (min, max) { var s = '#'; if (min < 1) { min = 1; } for (var i = 0, l = _rand.int(min, max); i < l; i++) { s += _rand.fromList('0123456789abcdefABCDEF'); } return s; }, numeric: function () { return '' + _rand.int(0, INT_MAX); }, integer: function () { if (_rand.bool() === true) { return '-' + this.numeric(); } return this.numeric(); }, decimal: function () { return this.integer() + '.' + this.numeric(); }, alpha: function (min, max) { var s = ''; if (min < 1) { min = 1; } for (var i = 0, l = _rand.int(min, max); i < l; i++) { s += _rand.fromList('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); } return s; }, alphaNumeric: function (min, max) { var s = ''; if (min < 1) { min = 1; } for (var i = 0, l = _rand.int(min, max); i < l; i++) { s += _rand.fromList('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'); } return s; }, alphaDash: function (min, max) { var s = ''; if (min < 1) { min = 1; } for (var i = 0, l = _rand.int(min, max); i < l; i++) { s += _rand.fromList('_-abcdefghijklmnopqrstuvwxyz_-ABCDEFGHIJKLMNOPQRSTUVWXYZ_-0123456789_-'); } return s; }, javascript: function (min, max) { var s = _rand.fromList('_$abcdefghijklmnopqrstuvwxyz_$ABCDEFGHIJKLMNOPQRSTUVWXYZ_$'); for (var i = 0, l = _rand.int(min, max - 1); i < l; i++) { s += _rand.fromList('_$abcdefghijklmnopqrstuvwxyz_$ABCDEFGHIJKLMNOPQRSTUVWXYZ_$0123456789_$'); } return s; } }; function _getLimits(schema) { var min = INT_MIN; var max = INT_MAX; if (schema.gte != null) { min = schema.gte; } else if (schema.gt != null) { min = schema.gt + 1; } if (schema.lte != null) { max = schema.lte; } else if (schema.lt != null) { max = schema.lt - 1; } // For old IE. /* eslint-disable object-shorthand */ return { min: min, max: max }; /* eslint-enable object-shorthand */ } var _typeGenerator = { string: function (schema) { if (schema.eq != null) { return schema.eq; } var s = ''; var minLength = schema.minLength != null ? schema.minLength : 0; var maxLength = schema.maxLength != null ? schema.maxLength : 32; if (typeof schema.pattern === 'string' && typeof _formatSample[schema.pattern] === 'function') { return _formatSample[schema.pattern](minLength, maxLength); } var l = schema.exactLength != null ? schema.exactLength : _rand.int(minLength, maxLength); for (var i = 0; i < l; i++) { s += _rand.char(32, 126); } return s; }, number: function (schema) { if (schema.eq != null) { return schema.eq; } var limit = _getLimits(schema); var n = _rand.float(limit.min, limit.max); if (schema.ne != null) { var ne = _typeIs.array(schema.ne) ? schema.ne : [schema.ne]; while (ne.indexOf(n) !== -1) { n = _rand.float(limit.min, limit.max); } } return n; }, integer: function (schema) { if (schema.eq != null) { return schema.eq; } var limit = _getLimits(schema); var n = _rand.int(limit.min, limit.max); if (schema.ne != null) { var ne = _typeIs.array(schema.ne) ? schema.ne : [schema.ne]; while (ne.indexOf(n) !== -1) { n = _rand.int(limit.min, limit.max); } } return n; }, boolean: function (schema) { if (schema.eq != null) { return schema.eq; } return _rand.bool(); }, null: function (schema) { return null; }, date: function (schema) { if (schema.eq != null) { return schema.eq; } return new Date(); }, object: function (schema) { var o = {}; var prop = schema.properties || {}; for (var key in prop) { if (Object.prototype.hasOwnProperty.call(prop, key)) { if (prop[key].optional === true && _rand.bool() === true) { continue; } if (key !== '*') { o[key] = this.generate(prop[key]); } else { var rk = '__random_key_'; var randomKey = rk + 0; var n = _rand.int(1, 9); for (var i = 1; i <= n; i++) { if (!(randomKey in prop)) { o[randomKey] = this.generate(prop[key]); } randomKey = rk + i; } } } } return o; }, array: function (schema) { var self = this; var items = schema.items || {}; var minLength = schema.minLength != null ? schema.minLength : 0; var maxLength = schema.maxLength != null ? schema.maxLength : 16; var type; var candidate; var size; var i; if (_typeIs.array(items)) { size = items.length; if (schema.exactLength != null) { size = schema.exactLength; } else if (size < minLength) { size = minLength; } else if (size > maxLength) { size = maxLength; } candidate = new Array(size); type = null; for (i = 0; i < size; i++) { type = items[i].type || 'any'; if (_typeIs.array(type)) { type = type[_rand.int(0, type.length - 1)]; } candidate[i] = self[type](items[i]); } } else { size = schema.exactLength != null ? schema.exactLength : _rand.int(minLength, maxLength); candidate = new Array(size); type = items.type || 'any'; if (_typeIs.array(type)) { type = type[_rand.int(0, type.length - 1)]; } for (i = 0; i < size; i++) { candidate[i] = self[type](items); } } return candidate; }, any: function (schema) { var fields = Object.keys(_typeGenerator); var i = fields[_rand.int(0, fields.length - 2)]; return this[i](schema); } }; // CandidateGenerator Class (Singleton) -------------------------------------- function CandidateGenerator() { // Maybe extends Inspection class too ? } _extend(CandidateGenerator.prototype, _typeGenerator); var _instance = null; CandidateGenerator.instance = function () { if (!(_instance instanceof CandidateGenerator)) { _instance = new CandidateGenerator(); } return _instance; }; CandidateGenerator.prototype.generate = function (schema) { var type = schema.type || 'any'; if (_typeIs.array(type)) { type = type[_rand.int(0, type.length - 1)]; } return this[type](schema); }; // Exports --------------------------------------------------------------------- var SchemaInspector = {}; // if server-side (node.js) else client-side if (typeof module !== 'undefined' && module.exports) { module.exports = SchemaInspector; } else { window.SchemaInspector = SchemaInspector; } SchemaInspector.newSanitization = function (schema, custom) { return new Sanitization(schema, custom); }; SchemaInspector.newValidation = function (schema, custom) { return new Validation(schema, custom); }; SchemaInspector.Validation = Validation; SchemaInspector.Sanitization = Sanitization; SchemaInspector.sanitize = function (schema, post, custom, callback) { if (arguments.length === 3 && typeof custom === 'function') { callback = custom; custom = null; } return new Sanitization(schema, custom).sanitize(post, callback); }; SchemaInspector.validate = function (schema, candidate, custom, callback) { if (arguments.length === 3 && typeof custom === 'function') { callback = custom; custom = null; } return new Validation(schema, custom).validate(candidate, callback); }; SchemaInspector.generate = function (schema, n) { if (typeof n === 'number') { var r = new Array(n); for (var i = 0; i < n; i++) { r[i] = CandidateGenerator.instance().generate(schema); } return r; } return CandidateGenerator.instance().generate(schema); }; })();