diff --git a/client/lib/std-additions/array.js b/client/lib/std-additions/array.js deleted file mode 100644 index 05fc9cf..0000000 --- a/client/lib/std-additions/array.js +++ /dev/null @@ -1,3 +0,0 @@ -if (typeof Array.isArray !== 'function') { - Array.isArray = function(obj) { return toString.call(obj) === "[object Array]"; }; -} diff --git a/client/lib/std-additions/array.js b/client/lib/std-additions/array.js new file mode 120000 index 0000000..9aa5e4f --- /dev/null +++ b/client/lib/std-additions/array.js @@ -0,0 +1 @@ +../../../oui/std-additions/array.js \ No newline at end of file diff --git a/client/lib/std-additions/object.js b/client/lib/std-additions/object.js new file mode 120000 index 0000000..60f5fcd --- /dev/null +++ b/client/lib/std-additions/object.js @@ -0,0 +1 @@ +../../../oui/std-additions/object.js \ No newline at end of file diff --git a/oui/std-additions/array.js b/oui/std-additions/array.js new file mode 100644 index 0000000..f14cabc --- /dev/null +++ b/oui/std-additions/array.js @@ -0,0 +1,81 @@ +// NOTE: this file is used by both the server and client library, thus it need +// to work in web browsers. + +if (typeof Array.isArray !== 'function') { + Array.isArray = function(obj) { return toString.call(obj) === "[object Array]"; }; +} + +/** + * Return the first true return value from fun which is called for each value. + * fun is called on this and receives a single argument (current value). + */ +Array.prototype.find = function (fun) { + for (var i = 0, r; i < this.length; i++) + if ((r = fun.call(this, this[i]))) return r; +}; + +/** Return a (possibly new) array which only contains unique values. */ +Array.prototype.unique = function() { + var i, tag, m = {}; + for (i=0; (tag = this[i]); ++i) m[tag] = true; + m = Object.keys(m); + return (m.length === this.length) ? this : m; +}; + +/** + * Difference between this and other array. + * + * Returns a new array with values (or indices if returnIndices) which are not + * at the same place. + * + * Example 1: + * + * oldTags = ['computer', 'car']; + * newTags = ['car', 'computer', '80s']; + * oldTags.diff(newTags) --> ['80s'] + * + * Example 2: + * + * A = [1, 2, 3, 4, 5] + * B = [1, 2, 6, 4, 5, 6, 7, 8] + * B.diff(A) => [3] // values + * B.diff(A, true) => [2] // indices + * A.diff(B) => [8, 7, 6, 6] // values + * A.diff(B, true) => [7, 6, 5, 2] // indices + */ +Array.prototype.diff = function (other, returnIndices) { + var d = [], e = -1, h, i, j, k; + for(i = other.length, k = this.length; i--;){ + for(j = k; j && (h = other[i] !== this[--j]);){} + // The comparator here will be optimized away by V8 + if (h) (d[++e] = returnIndices ? i : other[i]); + } + return d; +}; + +/** + * Return a new array which contains the intersection of this and any other + * array passed as an argument. + */ +Array.prototype.intersect = function() { + var retArr = [], k1, arr, i, k; + arr1keys: + for (k1=0,L=this.length; k1. -// - obj.name += 3 will fail -// - obj.name = other will fail -// - delete obj.name will fail -// However, only simple types (strings, numbers, language constants) will be -// truly immutable. Complex types (arrays, objects) will still be mutable. -Object.defineConstant = function (obj, name, value, enumerable, deep) { - Object.defineProperty(obj, name, { - value: value, - writable: false, - enumerable: enumerable !== undefined ? (!!enumerable) : true, - configurable: false - }); -} +require('./object'); + //------------------------------------------------------------------------------ // Array -/** - * Return the first true return value from fun which is called for each value. - * fun is called on this and receives a single argument (current value). - */ -Array.prototype.find = function (fun) { - for (var i = 0, r; i < this.length; i++) - if (r = fun.call(this, this[i])) return r; -}; - -/** Return a (possibly new) array which only contains unique values. */ -Array.prototype.unique = function() { - const x = 1; - var i, tag, m = {}; - for (i=0; (tag = this[i]); ++i) m[tag] = x; - m = Object.keys(m); - return (m.length === this.length) ? this : m; -} - -/** - * Difference between this and other array. - * - * Returns a new array with values (or indices if returnIndices) which are not - * at the same place. - * - * Example 1: - * - * oldTags = ['computer', 'car']; - * newTags = ['car', 'computer', '80s']; - * oldTags.diff(newTags) --> ['80s'] - * - * Example 2: - * - * A = [1, 2, 3, 4, 5] - * B = [1, 2, 6, 4, 5, 6, 7, 8] - * B.diff(A) => [3] // values - * B.diff(A, true) => [2] // indices - * A.diff(B) => [8, 7, 6, 6] // values - * A.diff(B, true) => [7, 6, 5, 2] // indices - */ -Array.prototype.diff = function (other, returnIndices) { - var d = [], e = -1, h, i, j, k; - for(i = other.length, k = this.length; i--;){ - for(j = k; j && (h = other[i] !== this[--j]);); - // The comparator here will be optimized away by V8 - h && (d[++e] = returnIndices ? i : other[i]); - } - return d; -} - -/** - * Return a new array which contains the intersection of this and any other - * array passed as an argument. - */ -Array.prototype.intersect = function() { - var retArr = [], k1, arr, i, k; - arr1keys: - for (k1=0,L=this.length; k1. +// - obj.name += 3 will fail +// - obj.name = other will fail +// - delete obj.name will fail +// However, only simple types (strings, numbers, language constants) will be +// truly immutable. Complex types (arrays, objects) will still be mutable. +if (typeof Object.defineConstant !== 'function') { + if (typeof Object.defineProperty === 'function') { + Object.defineConstant = function (obj, name, value, enumerable, deep) { + Object.defineProperty(obj, name, { + value: value, + writable: false, + enumerable: enumerable !== undefined ? (!!enumerable) : true, + configurable: false + }); + }; + } else { + // better than nothing I guess... + Object.defineConstant = function (obj, name, value, enumerable, deep) { + obj[name] = value; + }; + } +} + if (typeof Object.keys !== 'function') { Object.keys = function(obj){ var keys = []; @@ -184,8 +211,18 @@ if (typeof Object.merge3 !== 'function') { } if (k in o) { - if (!Object.deepEquals(v, ov)) - updatedInA[k] = v; + if (!Object.deepEquals(v, ov)) { + if (typeof v === 'object' && !Array.isArray(v)) { + if (Object.keys(v).length === 0) { + if (Object.keys(r[k]).length === 0) + updatedInA[k] = v; + } else { + updatedInA[k] = v; + } + } else { + updatedInA[k] = v; + } + } } else { newInA[k] = v; } diff --git a/oui/util.js b/oui/util.js index f83561a..260344e 100644 --- a/oui/util.js +++ b/oui/util.js @@ -168,3 +168,79 @@ CallQueue.prototype.performNext = function() { this.autostart = false; } } + +// ----------------------------------------------------------------------------- +// Input sanitation + +exports.urlRegExp = /\b(([\w-]+:\/\/?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|\/)))/; +exports.emailRegExp = /^[^@]+@[^@]+\.[^@]+$/; + +exports.sanitizeInput = function (params, dst, accepts) { + var k, e, type, def, value, ok, dstbuf = {}; + for (k in accepts) { def = accepts[k]; + // not set? + if (!(k in params) || (value = params[k]) === undefined) { + // required and missing? + if (def.required) { + return ((e = new Error('missing parameter "'+k+'"')) + && (e.statusCode = 400) && e); + } + // it's optional, so lets simply skip it + continue; + } + // retrieve value + value = params[k]; + type = typeof value; + // check type + if (def.type) { + if (def.type === 'array') { + ok = Array.isArray(value); + } else if (def.type === 'url') { + ok = String(value).match(exports.urlRegExp); + } else if (def.type === 'email') { + ok = String(value).match(exports.emailRegExp); + } else if (def.type.substr(0,3) === 'int') { + if ((ok = (type === 'number'))) + value = Math.round(value); + } else if ((ok = (def.type === type)) && (def.type === 'number')) { + ok = !isNaN(value); + } + if (!ok) { + return ((e = new Error('bad type of parameter "'+k+'" -- expected '+def.type)) + && (e.statusCode = 400) && e); + } + } + // trim strings + if (type === 'string') { + value = value.trim(); + } + // empty string? + if (def.empty !== undefined && !def.empty && type !== 'number') { + ok = true; + if (type === 'string') { + ok = (value.length !== 0); + } else if (type === 'object') { + ok = (Array.isArray(value) ? value.length : Object.keys(value).length) !== 0; + } + if (!ok) { + return ((e = new Error('empty parameter "'+k+'"')) + && (e.statusCode = 400) && e); + } + } + // check regexp match + if (def.match && !String(value).match(def.match)) { + return ((e = new Error('bad format of argument "'+k+'" -- expected '+def.match)) + && (e.statusCode = 400) && e); + } + // post-filter + if (typeof def.filter === 'function') { + value = def.filter(value); + } + // accepted + dstbuf[k] = value; + } + // all ok -- apply dstbuf to dst + for (k in dstbuf) dst[k] = dstbuf[k]; + // return a false value to indicate there was no error + return null; +}