From 2d5d6999541add350fb396ef02dc42ca3215049e Mon Sep 17 00:00:00 2001 From: Chris O'Hara <cohara87@gmail.com> Date: Thu, 31 Oct 2013 11:41:57 +1100 Subject: [PATCH] Remove the XSS filter. The xss() function was originally a port of the XSS filter from CodeIgniter. I added it to the library because there wasn't an alternative at the time. Unfortunately I don't have the time or expertise to maintain the XSS filter or keep merging upstream changes. If you need one for your app, I suggest looking at Caja sanitisation engine maintained by Google. (https://code.google.com/p/google-caja/ source/browse/trunk/src/com/google/caja/plugin/html-sanitizer.js) Closes #123, #138, #181, #206, #210, #221, #223, #226, #227, #231, #232 --- README.md | 5 - lib/filter.js | 6 -- lib/xss.js | 228 -------------------------------------------- package.json | 2 +- test/filter.test.js | 33 ------- validator-min.js | 2 +- validator.js | 209 +--------------------------------------- 7 files changed, 3 insertions(+), 482 deletions(-) delete mode 100755 lib/xss.js diff --git a/README.md b/README.md index 528e01139..36d36f544 100755 --- a/README.md +++ b/README.md @@ -34,7 +34,6 @@ var int = sanitize('0123').toInt(); //123 var bool = sanitize('true').toBoolean(); //true var str = sanitize(' \t\r hello \n').trim(); //'hello' var str = sanitize('aaaaaaaaab').ltrim('a'); //'b' -var str = sanitize(large_input_str).xss(); var str = sanitize('<a>').entityDecode(); //'<a>' ``` @@ -58,7 +57,6 @@ get('/', function (req, res) { req.checkHeader('referer').contains('localhost'); //Sanitize user input - req.sanitize('textarea').xss(); req.sanitize('foo').toBoolean(); //etc. @@ -130,8 +128,6 @@ toBooleanStrict() //False unless str = '1' or 'true' entityDecode() //Decode HTML entities entityEncode() escape() //Escape &, <, >, and " -xss() //Remove common XSS attack vectors from user-supplied HTML -xss(true) //Remove common XSS attack vectors from images ``` ## Extending the library @@ -221,7 +217,6 @@ var errors = validator.getErrors(); // ['Invalid email', 'String is too small'] - [oris](https://github.com/orls) - Added in() - [mren](https://github.com/mren) - Decoupled rules - [Thorsten Basse](https://github.com/tbasse) - Cleanup and refinement of existing validators -- [Neal Poole](https://github.com/nealpoole) - Port the latest xss() updates from CodeIgniter ## LICENSE diff --git a/lib/filter.js b/lib/filter.js index f828c33ee..c739d8498 100755 --- a/lib/filter.js +++ b/lib/filter.js @@ -1,5 +1,4 @@ var entities = require('./entities'); -var xss = require('./xss'); var Filter = exports.Filter = function() {} @@ -28,11 +27,6 @@ Filter.prototype.convert = Filter.prototype.sanitize = function(str) { return this; } -Filter.prototype.xss = function(is_image) { - this.modify(xss.clean(this.str, is_image)); - return this.wrap(this.str); -} - Filter.prototype.entityDecode = function() { this.modify(entities.decode(this.str)); return this.wrap(this.str); diff --git a/lib/xss.js b/lib/xss.js deleted file mode 100755 index 4a2ed9e66..000000000 --- a/lib/xss.js +++ /dev/null @@ -1,228 +0,0 @@ -//This module is adapted from the CodeIgniter framework -//The license is available at http://codeigniter.com/ - -var html_entity_decode = require('./entities').decode; - -var never_allowed_str = { - 'document.cookie': '[removed]', - 'document.write': '[removed]', - '.parentNode': '[removed]', - '.innerHTML': '[removed]', - 'window.location': '[removed]', - '-moz-binding': '[removed]', - '<!--': '<!--', - '-->': '-->', - '(<!\\[CDATA\\[)': '<![CDATA[', - '<comment>': '<comment>' -}; - -var never_allowed_regex = { - 'javascript\\s*:': '[removed]', - 'expression\\s*(\\(|()': '[removed]', - 'vbscript\\s*:': '[removed]', - 'Redirect\\s+302': '[removed]', - "([\"'])?data\\s*:[^\\1]*?base64[^\\1]*?,[^\\1]*?\\1?": '[removed]' -}; - -var non_displayables = [ - /%0[0-8bcef]/g, // url encoded 00-08, 11, 12, 14, 15 - /%1[0-9a-f]/g, // url encoded 16-31 - /[\x00-\x08]/g, // 00-08 - /\x0b/g, /\x0c/g, // 11,12 - /[\x0e-\x1f]/g // 14-31 -]; - -var compact_words = [ - 'javascript', 'expression', 'vbscript', - 'script', 'base64', 'applet', 'alert', - 'document', 'write', 'cookie', 'window' -]; - -exports.clean = function(str, is_image) { - - //Remove invisible characters - str = remove_invisible_characters(str); - - //Protect query string variables in URLs => 901119URL5918AMP18930PROTECT8198 - var hash; - do { - // ensure str does not contain hash before inserting it - hash = xss_hash(); - } while(str.indexOf(hash) >= 0) - str = str.replace(/\&([a-z\_0-9\-]+)\=([a-z\_0-9\-]+)/ig, hash + '$1=$2'); - - //Validate standard character entities. Add a semicolon if missing. We do this to enable - //the conversion of entities to ASCII later. - str = str.replace(/(&#?[0-9a-z]{2,})([\x00-\x20])*;?/ig, '$1;$2'); - - //Validate UTF16 two byte encoding (x00) - just as above, adds a semicolon if missing. - str = str.replace(/(&#x?)([0-9A-F]+);?/ig, '$1$2;'); - - //Un-protect query string variables - str = str.replace(new RegExp(hash, 'g'), '&'); - - //Decode just in case stuff like this is submitted: - //<a href="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a> - try{ - str = decodeURIComponent(str); - } - catch(error){ - // str was not actually URI-encoded - } - - //Convert character entities to ASCII - this permits our tests below to work reliably. - //We only convert entities that are within tags since these are the ones that will pose security problems. - str = str.replace(/[a-z]+=([\'\"]).*?\1/gi, function(m, match) { - return m.replace(match, convert_attribute(match)); - }); - str = str.replace(/<\w+.*/gi, function(m) { - return m.replace(m, html_entity_decode(m)); - }); - - //Remove invisible characters again - str = remove_invisible_characters(str); - - //Convert tabs to spaces - str = str.replace('\t', ' '); - - //Captured the converted string for later comparison - var converted_string = str; - - //Remove strings that are never allowed - for (var i in never_allowed_str) { - str = str.replace(new RegExp(i, "gi"), never_allowed_str[i]); - } - - //Remove regex patterns that are never allowed - for (var i in never_allowed_regex) { - str = str.replace(new RegExp(i, 'gi'), never_allowed_regex[i]); - } - - //Compact any exploded words like: j a v a s c r i p t - // We only want to do this when it is followed by a non-word character - for (var i = 0, l = compact_words.length; i < l; i++) { - var spacified = compact_words[i].split('').join('\\s*')+'\\s*'; - - str = str.replace(new RegExp('('+spacified+')(\\W)', 'ig'), function(m, compat, after) { - return compat.replace(/\s+/g, '') + after; - }); - } - - //Remove disallowed Javascript in links or img tags - do { - var original = str; - - if (str.match(/<a/i)) { - str = str.replace(/<a\s+([^>]*?)(>|$)/gi, function(m, attributes, end_tag) { - var filtered_attributes = filter_attributes(attributes.replace('<','').replace('>','')); - filtered_attributes = filtered_attributes.replace(/href=.*?(?:alert\(|alert(|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|data\s*:)/gi, ''); - return m.replace(attributes, filtered_attributes); - }); - } - - if (str.match(/<img/i)) { - str = str.replace(/<img\s+([^>]*?)(\s?\/?>|$)/gi, function(m, attributes, end_tag) { - var filtered_attributes = filter_attributes(attributes.replace('<','').replace('>','')); - filtered_attributes = filtered_attributes.replace(/src=.*?(?:alert\(|alert(|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)/gi, ''); - return m.replace(attributes, filtered_attributes); - }); - } - - if (str.match(/script/i) || str.match(/xss/i)) { - str = str.replace(/<(\/*)(script|xss)(.*?)\>/gi, '[removed]'); - } - - } while(original !== str); - - // Remove Evil HTML Attributes (like event handlers and style) - var event_handlers = ['\\bon\\w*', '\\bstyle', '\\bformaction']; - - //Adobe Photoshop puts XML metadata into JFIF images, including namespacing, - //so we have to allow this for images - if (!is_image) { - event_handlers.push('xmlns'); - } - - do { - var attribs = []; - var count = 0; - - attribs = attribs.concat(str.match(new RegExp("("+event_handlers.join('|')+")\\s*=\\s*(\\x22|\\x27)([^\\2]*?)(\\2)", 'ig'))); - attribs = attribs.concat(str.match(new RegExp("("+event_handlers.join('|')+")\\s*=\\s*([^\\s>]*)", 'ig'))); - attribs = attribs.filter(function(element) { return element !== null; }); - - if (attribs.length > 0) { - for (var i = 0; i < attribs.length; ++i) { - attribs[i] = attribs[i].replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'), '\\$&') - } - - str = str.replace(new RegExp("(<?)(\/?[^><]+?)([^A-Za-z<>\\-])(.*?)("+attribs.join('|')+")(.*?)([\\s><]?)([><]*)", 'i'), function(m, a, b, c, d, e, f, g, h) { - ++count; - return a + b + ' ' + d + f + g + h; - }); - } - } while (count > 0); - - //Sanitize naughty HTML elements - //If a tag containing any of the words in the list - //below is found, the tag gets converted to entities. - //So this: <blink> - //Becomes: <blink> - var naughty = 'alert|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|isindex|layer|link|meta|object|plaintext|style|script|textarea|title|video|xml|xss'; - str = str.replace(new RegExp('<(/*\\s*)('+naughty+')([^><]*)([><]*)', 'gi'), function(m, a, b, c, d) { - return '<' + a + b + c + d.replace('>','>').replace('<','<'); - }); - - //Sanitize naughty scripting elements Similar to above, only instead of looking for - //tags it looks for PHP and JavaScript commands that are disallowed. Rather than removing the - //code, it simply converts the parenthesis to entities rendering the code un-executable. - //For example: eval('some code') - //Becomes: eval('some code') - str = str.replace(/(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)/gi, '$1$2($3)'); - - //This adds a bit of extra precaution in case something got through the above filters - for (var i in never_allowed_str) { - str = str.replace(new RegExp(i, "gi"), never_allowed_str[i]); - } - for (var i in never_allowed_regex) { - str = str.replace(new RegExp(i, 'gi'), never_allowed_regex[i]); - } - - //Images are handled in a special way - if (is_image && str !== converted_string) { - throw new Error('Image may contain XSS'); - } - - return str; -} - -function remove_invisible_characters(str) { - for (var i = 0, l = non_displayables.length; i < l; i++) { - str = str.replace(non_displayables[i], ''); - } - return str; -} - -function xss_hash() { - var str = '', num = 10; - while (num--) str += String.fromCharCode(Math.random() * 25 | 97); - return str; -} - -function convert_attribute(str) { - return str.replace('>','>').replace('<','<').replace('\\','\\\\'); -} - -function filter_attributes(str) { - var result = ""; - - var match = str.match(/\s*[a-z-]+\s*=\s*(\x22|\x27)([^\1]*?)\1/ig); - if (match) { - for (var i = 0; i < match.length; ++i) { - result += match[i].replace(/\*.*?\*/g, ''); - } - } - - return result; -} - diff --git a/package.json b/package.json index f2b7bcd57..bf5f62f02 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "description" : "Data validation, filtering and sanitization for node.js", "version" : "1.5.1", "homepage" : "http://github.com/chriso/node-validator", - "keywords" : ["validator", "validation", "assert", "params", "sanitization", "xss", "entities", "sanitize", "sanitisation", "input"], + "keywords" : ["validator", "validation", "assert", "params", "sanitization", "entities", "sanitize", "sanitisation", "input"], "author" : "Chris O'Hara <cohara87@gmail.com>", "main" : "./lib", "directories" : { "lib" : "./lib" }, diff --git a/test/filter.test.js b/test/filter.test.js index 65f3b68c4..99e288b17 100755 --- a/test/filter.test.js +++ b/test/filter.test.js @@ -132,39 +132,6 @@ module.exports = { assert.equal('½', Filter.sanitize('½').entityEncode()); }, - 'test #xss()': function () { - //Need more tests! - assert.equal('[removed] foobar', Filter.sanitize('javascript : foobar').xss()); - assert.equal('[removed] foobar', Filter.sanitize('j a vasc ri pt: foobar').xss()); - assert.equal('<a >some text</a>', Filter.sanitize('<a href="javascript:alert(\'xss\')">some text</a>').xss()); - - assert.equal('<s <> <s >This is a test</s>', Filter.sanitize('<s <onmouseover="alert(1)"> <s onmouseover="alert(1)">This is a test</s>').xss()); - assert.equal('<a >">test</a>', Filter.sanitize('<a href="javascriptJ a V a S c R iPt::alert(1)" "<s>">test</a>').xss()); - assert.equal('<div ><h1>You have won</h1>Please click the link and enter your login details: <a href="http://example.com/">http://good.com</a></div>', Filter.sanitize('<div style="z-index: 9999999; background-color: green; width: 100%; height: 100%"><h1>You have won</h1>Please click the link and enter your login details: <a href="http://example.com/">http://good.com</a></div>').xss()); - assert.equal('<scrRedirec[removed]t 302ipt type="text/javascript">prompt(1);</scrRedirec[removed]t 302ipt>', Filter.sanitize('<scrRedirecRedirect 302t 302ipt type="text/javascript">prompt(1);</scrRedirecRedirect 302t 302ipt>').xss()); - assert.equal('<img src="a" ', Filter.sanitize('<img src="a" onerror=\'eval(atob("cHJvbXB0KDEpOw=="))\'').xss()); - - - // Source: http://blog.kotowicz.net/2012/07/codeigniter-210-xssclean-cross-site.html - assert.equal('<img src=">" >', Filter.sanitize('<img/src=">" onerror=alert(1)>').xss()); - assert.equal('<button a=">" autofocus ></button>', Filter.sanitize('<button/a=">" autofocus onfocus=alert(1(></button>').xss()); - assert.equal('<button a=">" autofocus >', Filter.sanitize('<button a=">" autofocus onfocus=alert(1(>').xss()); - assert.equal('<a target="_blank">clickme in firefox</a>', Filter.sanitize('<a target="_blank" href="data:text/html;BASE64youdummy,PHNjcmlwdD5hbGVydCh3aW5kb3cub3BlbmVyLmRvY3VtZW50LmRvY3VtZW50RWxlbWVudC5pbm5lckhUTUwpPC9zY3JpcHQ+">clickme in firefox</a>').xss()); - assert.equal('<a/\'\'\' target="_blank" href=[removed]PHNjcmlwdD5hbGVydChvcGVuZXIuZG9jdW1lbnQuYm9keS5pbm5lckhUTUwpPC9zY3JpcHQ+>firefox11</a>', Filter.sanitize('<a/\'\'\' target="_blank" href=data:text/html;;base64,PHNjcmlwdD5hbGVydChvcGVuZXIuZG9jdW1lbnQuYm9keS5pbm5lckhUTUwpPC9zY3JpcHQ+>firefox11</a>').xss()); - - var url = 'http://www.example.com/test.php?a=b&b=c&c=d'; - assert.equal(url, Filter.sanitize(url).xss()); - }, - - 'test chaining': function () { - assert.equal('&amp;amp;', Filter.sanitize('&').chain().entityEncode().entityEncode().entityEncode().value()); - - //Return the default behaviour - Filter.wrap = function (str) { - return str; - } - }, - 'test #escape': function () { assert.equal('&<">', Filter.sanitize('&<">').escape()); } diff --git a/validator-min.js b/validator-min.js index 9b39281cc..5cf9befdc 100644 --- a/validator-min.js +++ b/validator-min.js @@ -20,4 +20,4 @@ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -(function(root,factory){if(typeof define === 'function' && define.amd){define(['exports'],factory);}else if(typeof exports === 'object'){factory(exports);}else{factory(root);}}(this,function(t){var r={" ":" ","¡":"¡","¢":"¢","£":"£","¤":"€","¥":"¥","¦":"Š","§":"§","¨":"š","©":"©","ª":"ª","«":"«","¬":"¬","­":"","®":"®","¯":"¯","°":"°","±":"±","²":"²","³":"³","´":"Ž","µ":"µ","¶":"¶","·":"·","¸":"ž","¹":"¹","º":"º","»":"»","¼":"Œ","½":"œ","¾":"Ÿ","¿":"¿","À":"À","Á":"Á","Â":"Â","Ã":"Ã","Ä":"Ä","Å":"Å","Æ":"Æ","Ç":"Ç","È":"È","É":"É","Ê":"Ê","Ë":"Ë","Ì":"Ì","Í":"Í","Î":"Î","Ï":"Ï","Ð":"Ð","Ñ":"Ñ","Ò":"Ò","Ó":"Ó","Ô":"Ô","Õ":"Õ","Ö":"Ö","×":"×","Ø":"Ø","Ù":"Ù","Ú":"Ú","Û":"Û","Ü":"Ü","Ý":"Ý","Þ":"Þ","ß":"ß","à":"à","á":"á","â":"â","ã":"ã","ä":"ä","å":"å","æ":"æ","ç":"ç","è":"è","é":"é","ê":"ê","ë":"ë","ì":"ì","í":"í","î":"î","ï":"ï","ð":"ð","ñ":"ñ","ò":"ò","ó":"ó","ô":"ô","õ":"õ","ö":"ö","÷":"÷","ø":"ø","ù":"ù","ú":"ú","û":"û","ü":"ü","ý":"ý","þ":"þ","ÿ":"ÿ",""":'"',"<":"<",">":">","'":"'","−":"−","ˆ":"ˆ","˜":"˜","Š":"Š","‹":"‹","Œ":"Œ","‘":"‘","’":"’","“":"“","”":"”","•":"•","–":"–","—":"—","™":"™","š":"š","›":"›","œ":"œ","Ÿ":"Ÿ","ƒ":"ƒ","Α":"Α","Β":"Β","Γ":"Γ","Δ":"Δ","Ε":"Ε","Ζ":"Ζ","Η":"Η","Θ":"Θ","Ι":"Ι","Κ":"Κ","Λ":"Λ","Μ":"Μ","Ν":"Ν","Ξ":"Ξ","Ο":"Ο","Π":"Π","Ρ":"Ρ","Σ":"Σ","Τ":"Τ","Υ":"Υ","Φ":"Φ","Χ":"Χ","Ψ":"Ψ","Ω":"Ω","α":"α","β":"β","γ":"γ","δ":"δ","ε":"ε","ζ":"ζ","η":"η","θ":"θ","ι":"ι","κ":"κ","λ":"λ","μ":"μ","ν":"ν","ξ":"ξ","ο":"ο","π":"π","ρ":"ρ","ς":"ς","σ":"σ","τ":"τ","υ":"υ","φ":"φ","χ":"χ","ψ":"ψ","ω":"ω","ϑ":"ϑ","ϒ":"ϒ","ϖ":"ϖ"," ":" "," ":" "," ":" ","‌":"","‍":"","‎":"","‏":"","‚":"‚","„":"„","†":"†","‡":"‡","…":"…","‰":"‰","′":"′","″":"″","‾":"‾","⁄":"⁄","€":"€","ℑ":"ℑ","℘":"℘","ℜ":"ℜ","ℵ":"ℵ","←":"←","↑":"↑","→":"→","↓":"↓","↔":"↔","↵":"↵","⇐":"⇐","⇑":"⇑","⇒":"⇒","⇓":"⇓","⇔":"⇔","∀":"∀","∂":"∂","∃":"∃","∅":"∅","∇":"∇","∈":"∈","∉":"∉","∋":"∋","∏":"∏","∑":"∑","∗":"∗","√":"√","∝":"∝","∞":"∞","∠":"∠","∧":"∧","∨":"∨","∩":"∩","∪":"∪","∫":"∫","∴":"∴","∼":"∼","≅":"≅","≈":"≈","≠":"≠","≡":"≡","≤":"≤","≥":"≥","⊂":"⊂","⊃":"⊃","⊄":"⊄","⊆":"⊆","⊇":"⊇","⊕":"⊕","⊗":"⊗","⊥":"⊥","⋅":"⋅","⌈":"⌈","⌉":"⌉","⌊":"⌊","⌋":"⌋","⟨":"〈","⟩":"〉","◊":"◊","♠":"♠","♣":"♣","♥":"♥","♦":"♦"};var e=function(t){if(!~t.indexOf("&"))return t;for(var e in r){t=t.replace(new RegExp(e,"g"),r[e])}t=t.replace(/&#x(0*[0-9a-f]{2,5});?/gi,function(t,r){return String.fromCharCode(parseInt(+r,16))});t=t.replace(/&#([0-9]{2,4});?/gi,function(t,r){return String.fromCharCode(+r)});t=t.replace(/&/g,"&");return t};var i=function(t){t=t.replace(/&/g,"&");t=t.replace(/'/g,"'");for(var e in r){t=t.replace(new RegExp(r[e],"g"),e)}return t};t.entities={encode:i,decode:e};var s={"document.cookie":"","document.write":"",".parentNode":"",".innerHTML":"","window.location":"","-moz-binding":"","<!--":"<!--","-->":"-->","<![CDATA[":"<![CDATA["};var n={"javascript\\s*:":"","expression\\s*(\\(|&\\#40;)":"","vbscript\\s*:":"","Redirect\\s+302":""};var a=[/%0[0-8bcef]/g,/%1[0-9a-f]/g,/[\x00-\x08]/g,/\x0b/g,/\x0c/g,/[\x0e-\x1f]/g];var o=["javascript","expression","vbscript","script","applet","alert","document","write","cookie","window"];t.xssClean=function(r,e){if(typeof r==="object"){for(var i in r){r[i]=t.xssClean(r[i])}return r}r=c(r);r=r.replace(/\&([a-z\_0-9]+)\=([a-z\_0-9]+)/i,u()+"$1=$2");r=r.replace(/(&\#?[0-9a-z]{2,})([\x00-\x20])*;?/i,"$1;$2");r=r.replace(/(&\#x?)([0-9A-F]+);?/i,"$1;$2");r=r.replace(u(),"&");try{r=decodeURIComponent(r)}catch(a){}r=r.replace(/[a-z]+=([\'\"]).*?\1/gi,function(t,r){return t.replace(r,p(r))});r=c(r);r=r.replace(" "," ");var l=r;for(var i in s){r=r.replace(i,s[i])}for(var i in n){r=r.replace(new RegExp(i,"i"),n[i])}for(var i in o){var f=o[i].split("").join("\\s*")+"\\s*";r=r.replace(new RegExp("("+f+")(\\W)","ig"),function(t,r,e){return r.replace(/\s+/g,"")+e})}do{var m=r;if(r.match(/<a/i)){r=r.replace(/<a\s+([^>]*?)(>|$)/gi,function(t,r,e){r=h(r.replace("<","").replace(">",""));return t.replace(r,r.replace(/href=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)/gi,""))})}if(r.match(/<img/i)){r=r.replace(/<img\s+([^>]*?)(\s?\/?>|$)/gi,function(t,r,e){r=h(r.replace("<","").replace(">",""));return t.replace(r,r.replace(/src=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)/gi,""))})}if(r.match(/script/i)||r.match(/xss/i)){r=r.replace(/<(\/*)(script|xss)(.*?)\>/gi,"")}}while(m!=r);event_handlers=["[^a-z_-]on\\w*"];if(!e){event_handlers.push("xmlns")}r=r.replace(new RegExp("<([^><]+?)("+event_handlers.join("|")+")(\\s*=\\s*[^><]*)([><]*)","i"),"<$1$4");naughty="alert|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|isindex|layer|link|meta|object|plaintext|style|script|textarea|title|video|xml|xss";r=r.replace(new RegExp("<(/*\\s*)("+naughty+")([^><]*)([><]*)","gi"),function(t,r,e,i,s){return"<"+r+e+i+s.replace(">",">").replace("<","<")});r=r.replace(/(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)/gi,"$1$2($3)");for(var i in s){r=r.replace(i,s[i])}for(var i in n){r=r.replace(new RegExp(i,"i"),n[i])}if(e&&r!==l){throw new Error("Image may contain XSS")}return r};function c(t){for(var r in a){t=t.replace(a[r],"")}return t}function u(){return"!*$^#(@*#&"}function p(t){return t.replace(">",">").replace("<","<").replace("\\","\\\\")}function h(t){var r=/\/\*.*?\*\//g;return t.replace(/\s*[a-z-]+\s*=\s*'[^']*'/gi,function(t){return t.replace(r,"")}).replace(/\s*[a-z-]+\s*=\s*"[^"]*"/gi,function(t){return t.replace(r,"")}).replace(/\s*[a-z-]+\s*=\s*[^\s]+/gi,function(t){return t.replace(r,"")})}var l=t.Validator=function(){};l.prototype.check=function(t,r){this.str=typeof t==="undefined"||t===null||isNaN(t)&&t.length===undefined?"":t+"";this.msg=r;this._errors=this._errors||[];return this};l.prototype.validate=l.prototype.check;l.prototype.assert=l.prototype.check;l.prototype.error=function(t){throw new Error(t)};function f(t){if(t instanceof Date){return t}var r=Date.parse(t);if(isNaN(r)){return null}return new Date(r)}l.prototype.isAfter=function(t){t=t||new Date;var r=f(this.str),e=f(t);if(!(r&&e&&r>=e)){return this.error(this.msg||"Invalid date")}return this};l.prototype.isBefore=function(t){t=t||new Date;var r=f(this.str),e=f(t);if(!(r&&e&&r<=e)){return this.error(this.msg||"Invalid date")}return this};l.prototype.isEmail=function(){if(!this.str.match(/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/)){return this.error(this.msg||"Invalid email")}return this};l.prototype.isCreditCard=function(){this.str=this.str.replace(/[^0-9]+/g,"");if(!this.str.match(/^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/)){return this.error(this.msg||"Invalid credit card")}var t=0;var r;var e;var i=false;for(var s=this.length-1;s>=0;s--){r=this.substring(s,s+1);e=parseInt(r,10);if(i){e*=2;if(e>=10){t+=e%10+1}else{t+=e}}else{t+=e}if(i){i=false}else{i=true}}if(t%10!==0){return this.error(this.msg||"Invalid credit card")}return this};l.prototype.isUrl=function(){if(!this.str.match(/^(?!mailto:)(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))|localhost)(?::\d{2,5})?(?:\/[^\s]*)?$/i)||this.str.length>2083){return this.error(this.msg||"Invalid URL")}return this};l.prototype.isIP=function(){if(!this.str.match(/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/)){return this.error(this.msg||"Invalid IP")}return this};l.prototype.isAlpha=function(){if(!this.str.match(/^[a-zA-Z]+$/)){return this.error(this.msg||"Invalid characters")}return this};l.prototype.isAlphanumeric=function(){if(!this.str.match(/^[a-zA-Z0-9]+$/)){return this.error(this.msg||"Invalid characters")}return this};l.prototype.isNumeric=function(){if(!this.str.match(/^-?[0-9]+$/)){return this.error(this.msg||"Invalid number")}return this};l.prototype.isLowercase=function(){if(!this.str.match(/^[a-z0-9]+$/)){return this.error(this.msg||"Invalid characters")}return this};l.prototype.isUppercase=function(){if(!this.str.match(/^[A-Z0-9]+$/)){return this.error(this.msg||"Invalid characters")}return this};l.prototype.isInt=function(){if(!this.str.match(/^(?:-?(?:0|[1-9][0-9]*))$/)){return this.error(this.msg||"Invalid integer")}return this};l.prototype.isDecimal=function(){if(!this.str.match(/^(?:-?(?:0|[1-9][0-9]*))?(?:\.[0-9]*)?$/)){return this.error(this.msg||"Invalid decimal")}return this};l.prototype.isFloat=function(){return this.isDecimal()};l.prototype.notNull=function(){if(this.str===""){return this.error(this.msg||"Invalid characters")}return this};l.prototype.isNull=function(){if(this.str!==""){return this.error(this.msg||"Invalid characters")}return this};l.prototype.notEmpty=function(){if(this.str.match(/^[\s\t\r\n]*$/)){return this.error(this.msg||"String is whitespace")}return this};l.prototype.equals=function(t){if(this.str!=t){return this.error(this.msg||"Not equal")}return this};l.prototype.contains=function(t){if(this.str.indexOf(t)===-1){return this.error(this.msg||"Invalid characters")}return this};l.prototype.notContains=function(t){if(this.str.indexOf(t)>=0){return this.error(this.msg||"Invalid characters")}return this};l.prototype.regex=l.prototype.is=function(t,r){if(Object.prototype.toString.call(t).slice(8,-1)!=="RegExp"){t=new RegExp(t,r)}if(!this.str.match(t)){return this.error(this.msg||"Invalid characters")}return this};l.prototype.notRegex=l.prototype.not=function(t,r){if(Object.prototype.toString.call(t).slice(8,-1)!=="RegExp"){t=new RegExp(t,r)}if(this.str.match(t)){this.error(this.msg||"Invalid characters")}return this};l.prototype.len=function(t,r){if(this.str.length<t){return this.error(this.msg||"String is too small")}if(typeof r!==undefined&&this.str.length>r){return this.error(this.msg||"String is too large")}return this};l.prototype.isUUID=function(t){var r;if(t==3||t=="v3"){r=/[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i}else if(t==4||t=="v4"){r=/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i}else{r=/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i}if(!this.str.match(r)){return this.error(this.msg||"Not a UUID")}return this};l.prototype.isDate=function(){var t=Date.parse(this.str);if(isNaN(t)){return this.error(this.msg||"Not a date")}return this};l.prototype.isIn=function(t){if(t&&typeof t.indexOf==="function"){if(!~t.indexOf(this.str)){return this.error(this.msg||"Unexpected value")}return this}else{return this.error(this.msg||"Invalid in() argument")}};l.prototype.notIn=function(t){if(t&&typeof t.indexOf==="function"){if(t.indexOf(this.str)!==-1){return this.error(this.msg||"Unexpected value")}return this}else{return this.error(this.msg||"Invalid notIn() argument")}};l.prototype.min=function(t){var r=parseFloat(this.str);if(!isNaN(r)&&r<t){return this.error(this.msg||"Invalid number")}return this};l.prototype.max=function(t){var r=parseFloat(this.str);if(!isNaN(r)&&r>t){return this.error(this.msg||"Invalid number")}return this};l.prototype.isArray=function(){if(!Array.isArray(this.str)){return this.error(this.msg||"Not an array")}return this};var m=t.Filter=function(){};var d="\\r\\n\\t\\s";m.prototype.modify=function(t){this.str=t};m.prototype.convert=m.prototype.sanitize=function(t){this.str=t==null?"":t+"";return this};m.prototype.xss=function(r){this.modify(t.xssClean(this.str,r));return this.str};m.prototype.entityDecode=function(){this.modify(e(this.str));return this.str};m.prototype.entityEncode=function(){this.modify(i(this.str));return this.str};m.prototype.escape=function(){this.modify(this.str.replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">"));return this.str};m.prototype.ltrim=function(t){t=t||d;this.modify(this.str.replace(new RegExp("^["+t+"]+","g"),""));return this.str};m.prototype.rtrim=function(t){t=t||d;this.modify(this.str.replace(new RegExp("["+t+"]+$","g"),""));return this.str};m.prototype.trim=function(t){t=t||d;this.modify(this.str.replace(new RegExp("^["+t+"]+|["+t+"]+$","g"),""));return this.str};m.prototype.ifNull=function(t){if(!this.str||this.str===""){this.modify(t)}return this.str};m.prototype.toFloat=function(){this.modify(parseFloat(this.str));return this.str};m.prototype.toInt=function(t){t=t||10;this.modify(parseInt(this.str,t));return this.str};m.prototype.toBoolean=function(){if(!this.str||this.str=="0"||this.str=="false"||this.str==""){this.modify(false)}else{this.modify(true)}return this.str};m.prototype.toBooleanStrict=function(){if(this.str=="1"||this.str=="true"){this.modify(true)}else{this.modify(false)}return this.str};t.sanitize=t.convert=function(r){var e=new t.Filter;return e.sanitize(r)};t.check=t.validate=t.assert=function(r,e){var i=new t.Validator;return i.check(r,e)};return t;})); +(function(e,t){typeof define=="function"&&define.amd?define(["exports"],t):typeof exports=="object"?t(exports):t(e)})(this,function(e){function s(e){if(/^(\d?\d?\d)\.(\d?\d?\d)\.(\d?\d?\d)\.(\d?\d?\d)$/.test(e)){var t=e.split(".").sort();return t[3]>255?!1:!0}return!1}function o(e){return/^::|^::1|^([a-fA-F0-9]{1,4}::?){1,7}([a-fA-F0-9]{1,4})$/.test(e)?!0:!1}function u(e){if(e instanceof Date)return e;var t=Date.parse(e);return isNaN(t)?null:new Date(t)}var t={" ":"\u00a0","¡":"\u00a1","¢":"\u00a2","£":"\u00a3","¤":"\u20ac","¥":"\u00a5","¦":"\u0160","§":"\u00a7","¨":"\u0161","©":"\u00a9","ª":"\u00aa","«":"\u00ab","¬":"\u00ac","­":"\u00ad","®":"\u00ae","¯":"\u00af","°":"\u00b0","±":"\u00b1","²":"\u00b2","³":"\u00b3","´":"\u017d","µ":"\u00b5","¶":"\u00b6","·":"\u00b7","¸":"\u017e","¹":"\u00b9","º":"\u00ba","»":"\u00bb","¼":"\u0152","½":"\u0153","¾":"\u0178","¿":"\u00bf","À":"\u00c0","Á":"\u00c1","Â":"\u00c2","Ã":"\u00c3","Ä":"\u00c4","Å":"\u00c5","Æ":"\u00c6","Ç":"\u00c7","È":"\u00c8","É":"\u00c9","Ê":"\u00ca","Ë":"\u00cb","Ì":"\u00cc","Í":"\u00cd","Î":"\u00ce","Ï":"\u00cf","Ð":"\u00d0","Ñ":"\u00d1","Ò":"\u00d2","Ó":"\u00d3","Ô":"\u00d4","Õ":"\u00d5","Ö":"\u00d6","×":"\u00d7","Ø":"\u00d8","Ù":"\u00d9","Ú":"\u00da","Û":"\u00db","Ü":"\u00dc","Ý":"\u00dd","Þ":"\u00de","ß":"\u00df","à":"\u00e0","á":"\u00e1","â":"\u00e2","ã":"\u00e3","ä":"\u00e4","å":"\u00e5","æ":"\u00e6","ç":"\u00e7","è":"\u00e8","é":"\u00e9","ê":"\u00ea","ë":"\u00eb","ì":"\u00ec","í":"\u00ed","î":"\u00ee","ï":"\u00ef","ð":"\u00f0","ñ":"\u00f1","ò":"\u00f2","ó":"\u00f3","ô":"\u00f4","õ":"\u00f5","ö":"\u00f6","÷":"\u00f7","ø":"\u00f8","ù":"\u00f9","ú":"\u00fa","û":"\u00fb","ü":"\u00fc","ý":"\u00fd","þ":"\u00fe","ÿ":"\u00ff",""":'"',"<":"<",">":">","'":"'","−":"\u2212","ˆ":"\u02c6","˜":"\u02dc","Š":"\u0160","‹":"\u2039","Œ":"\u0152","‘":"\u2018","’":"\u2019","“":"\u201c","”":"\u201d","•":"\u2022","–":"\u2013","—":"\u2014","™":"\u2122","š":"\u0161","›":"\u203a","œ":"\u0153","Ÿ":"\u0178","ƒ":"\u0192","Α":"\u0391","Β":"\u0392","Γ":"\u0393","Δ":"\u0394","Ε":"\u0395","Ζ":"\u0396","Η":"\u0397","Θ":"\u0398","Ι":"\u0399","Κ":"\u039a","Λ":"\u039b","Μ":"\u039c","Ν":"\u039d","Ξ":"\u039e","Ο":"\u039f","Π":"\u03a0","Ρ":"\u03a1","Σ":"\u03a3","Τ":"\u03a4","Υ":"\u03a5","Φ":"\u03a6","Χ":"\u03a7","Ψ":"\u03a8","Ω":"\u03a9","α":"\u03b1","β":"\u03b2","γ":"\u03b3","δ":"\u03b4","ε":"\u03b5","ζ":"\u03b6","η":"\u03b7","θ":"\u03b8","ι":"\u03b9","κ":"\u03ba","λ":"\u03bb","μ":"\u03bc","ν":"\u03bd","ξ":"\u03be","ο":"\u03bf","π":"\u03c0","ρ":"\u03c1","ς":"\u03c2","σ":"\u03c3","τ":"\u03c4","υ":"\u03c5","φ":"\u03c6","χ":"\u03c7","ψ":"\u03c8","ω":"\u03c9","ϑ":"\u03d1","ϒ":"\u03d2","ϖ":"\u03d6"," ":"\u2002"," ":"\u2003"," ":"\u2009","‌":"\u200c","‍":"\u200d","‎":"\u200e","‏":"\u200f","‚":"\u201a","„":"\u201e","†":"\u2020","‡":"\u2021","…":"\u2026","‰":"\u2030","′":"\u2032","″":"\u2033","‾":"\u203e","⁄":"\u2044","€":"\u20ac","ℑ":"\u2111","℘":"\u2118","ℜ":"\u211c","ℵ":"\u2135","←":"\u2190","↑":"\u2191","→":"\u2192","↓":"\u2193","↔":"\u2194","↵":"\u21b5","⇐":"\u21d0","⇑":"\u21d1","⇒":"\u21d2","⇓":"\u21d3","⇔":"\u21d4","∀":"\u2200","∂":"\u2202","∃":"\u2203","∅":"\u2205","∇":"\u2207","∈":"\u2208","∉":"\u2209","∋":"\u220b","∏":"\u220f","∑":"\u2211","∗":"\u2217","√":"\u221a","∝":"\u221d","∞":"\u221e","∠":"\u2220","∧":"\u2227","∨":"\u2228","∩":"\u2229","∪":"\u222a","∫":"\u222b","∴":"\u2234","∼":"\u223c","≅":"\u2245","≈":"\u2248","≠":"\u2260","≡":"\u2261","≤":"\u2264","≥":"\u2265","⊂":"\u2282","⊃":"\u2283","⊄":"\u2284","⊆":"\u2286","⊇":"\u2287","⊕":"\u2295","⊗":"\u2297","⊥":"\u22a5","⋅":"\u22c5","⌈":"\u2308","⌉":"\u2309","⌊":"\u230a","⌋":"\u230b","⟨":"\u2329","⟩":"\u232a","◊":"\u25ca","♠":"\u2660","♣":"\u2663","♥":"\u2665","♦":"\u2666"},n=function(e){if(!~e.indexOf("&"))return e;for(var n in t)e=e.replace(new RegExp(n,"g"),t[n]);return e=e.replace(/&#x(0*[0-9a-f]{2,5});?/gi,function(e,t){return String.fromCharCode(parseInt(+t,16))}),e=e.replace(/&#([0-9]{2,4});?/gi,function(e,t){return String.fromCharCode(+t)}),e=e.replace(/&/g,"&"),e},r=function(e){e=e.replace(/&/g,"&"),e=e.replace(/'/g,"'");for(var n in t)e=e.replace(new RegExp(t[n],"g"),n);return e};e.entities={encode:r,decode:n};var i=e.Validator=function(){};i.prototype.check=function(e,t){return this.str=typeof e=="undefined"||e===null||isNaN(e)&&e.length===undefined?"":e+"",this.msg=t,this._errors=this._errors||[],this},i.prototype.validate=i.prototype.check,i.prototype.assert=i.prototype.check,i.prototype.error=function(e){throw new Error(e)},i.prototype.isAfter=function(e){e=e||new Date;var t=u(this.str),n=u(e);return t&&n&&t>=n?this:this.error(this.msg||"Invalid date")},i.prototype.isBefore=function(e){e=e||new Date;var t=u(this.str),n=u(e);return t&&n&&t<=n?this:this.error(this.msg||"Invalid date")},i.prototype.isEmail=function(){return this.str.match(/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/)?this:this.error(this.msg||"Invalid email")},i.prototype.isCreditCard=function(){this.str=this.str.replace(/[^0-9]+/g,"");if(!this.str.match(/^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/))return this.error(this.msg||"Invalid credit card");var e=0,t,n,r=!1;for(var i=this.length-1;i>=0;i--)t=this.substring(i,i+1),n=parseInt(t,10),r?(n*=2,n>=10?e+=n%10+1:e+=n):e+=n,r?r=!1:r=!0;return e%10!==0?this.error(this.msg||"Invalid credit card"):this},i.prototype.isUrl=function(){return!this.str.match(/^(?!mailto:)(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))|localhost)(?::\d{2,5})?(?:\/[^\s]*)?$/i)||this.str.length>2083?this.error(this.msg||"Invalid URL"):this},i.prototype.isIPv4=function(){return s(this.str)?this:this.error(this.msg||"Invalid IP")},i.prototype.isIPv6=function(){return o(this.str)?this:this.error(this.msg||"Invalid IP")},i.prototype.isIP=function(){return s(this.str)||o(this.str)?this:this.error(this.msg||"Invalid IP")},i.prototype.isAlpha=function(){return this.str.match(/^[a-zA-Z]+$/)?this:this.error(this.msg||"Invalid characters")},i.prototype.isAlphanumeric=function(){return this.str.match(/^[a-zA-Z0-9]+$/)?this:this.error(this.msg||"Invalid characters")},i.prototype.isNumeric=function(){return this.str.match(/^-?[0-9]+$/)?this:this.error(this.msg||"Invalid number")},i.prototype.isHexadecimal=function(){return this.str.match(/^[0-9a-fA-F]+$/)?this:this.error(this.msg||"Invalid hexadecimal")},i.prototype.isHexColor=function(){return this.str.match(/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/)?this:this.error(this.msg||"Invalid hexcolor")},i.prototype.isLowercase=function(){return this.str!==this.str.toLowerCase()?this.error(this.msg||"Invalid characters"):this},i.prototype.isUppercase=function(){return this.str!==this.str.toUpperCase()?this.error(this.msg||"Invalid characters"):this},i.prototype.isInt=function(){return this.str.match(/^(?:-?(?:0|[1-9][0-9]*))$/)?this:this.error(this.msg||"Invalid integer")},i.prototype.isDecimal=function(){return this.str.match(/^(?:-?(?:0|[1-9][0-9]*))?(?:\.[0-9]*)?$/)?this:this.error(this.msg||"Invalid decimal")},i.prototype.isDivisibleBy=function(e){return parseFloat(this.str)%parseInt(e,10)===0},i.prototype.isFloat=function(){return this.isDecimal()},i.prototype.notNull=function(){return this.str===""?this.error(this.msg||"String is empty"):this},i.prototype.isNull=function(){return this.str!==""?this.error(this.msg||"String is not empty"):this},i.prototype.notEmpty=function(){return this.str.match(/^[\s\t\r\n]*$/)?this.error(this.msg||"String is whitespace"):this},i.prototype.equals=function(e){return this.str!=e?this.error(this.msg||"Not equal"):this},i.prototype.contains=function(e){return this.str.indexOf(e)===-1||!e?this.error(this.msg||"Invalid characters"):this},i.prototype.notContains=function(e){return this.str.indexOf(e)>=0?this.error(this.msg||"Invalid characters"):this},i.prototype.regex=i.prototype.is=function(e,t){return Object.prototype.toString.call(e).slice(8,-1)!=="RegExp"&&(e=new RegExp(e,t)),this.str.match(e)?this:this.error(this.msg||"Invalid characters")},i.prototype.notRegex=i.prototype.not=function(e,t){return Object.prototype.toString.call(e).slice(8,-1)!=="RegExp"&&(e=new RegExp(e,t)),this.str.match(e)&&this.error(this.msg||"Invalid characters"),this},i.prototype.len=function(e,t){return this.str.length<e?this.error(this.msg||"String is too small"):typeof t!==undefined&&this.str.length>t?this.error(this.msg||"String is too large"):this},i.prototype.isUUID=function(e){var t;return e==3||e=="v3"?t=/[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i:e==4||e=="v4"?t=/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i:e==5||e=="v5"?t=/^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i:t=/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i,this.str.match(t)?this:this.error(this.msg||"Not a UUID")},i.prototype.isUUIDv3=function(){return this.isUUID(3)},i.prototype.isUUIDv4=function(){return this.isUUID(4)},i.prototype.isUUIDv5=function(){return this.isUUID(5)},i.prototype.isDate=function(){var e=Date.parse(this.str);return isNaN(e)?this.error(this.msg||"Not a date"):this},i.prototype.isIn=function(e){return e&&typeof e.indexOf=="function"?~e.indexOf(this.str)?this:this.error(this.msg||"Unexpected value"):this.error(this.msg||"Invalid in() argument")},i.prototype.notIn=function(e){return e&&typeof e.indexOf=="function"?e.indexOf(this.str)!==-1?this.error(this.msg||"Unexpected value"):this:this.error(this.msg||"Invalid notIn() argument")},i.prototype.min=function(e){var t=parseFloat(this.str);return!isNaN(t)&&t<e?this.error(this.msg||"Invalid number"):this},i.prototype.max=function(e){var t=parseFloat(this.str);return!isNaN(t)&&t>e?this.error(this.msg||"Invalid number"):this};var a=e.Filter=function(){},f="\\r\\n\\t\\s";return a.prototype.modify=function(e){this.str=e},a.prototype.convert=a.prototype.sanitize=function(e){return this.str=e==null?"":e+"",this},a.prototype.entityDecode=function(){return this.modify(n(this.str)),this.str},a.prototype.entityEncode=function(){return this.modify(r(this.str)),this.str},a.prototype.escape=function(){return this.modify(this.str.replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")),this.str},a.prototype.ltrim=function(e){return e=e||f,this.modify(this.str.replace(new RegExp("^["+e+"]+","g"),"")),this.str},a.prototype.rtrim=function(e){return e=e||f,this.modify(this.str.replace(new RegExp("["+e+"]+$","g"),"")),this.str},a.prototype.trim=function(e){return e=e||f,this.modify(this.str.replace(new RegExp("^["+e+"]+|["+e+"]+$","g"),"")),this.str},a.prototype.ifNull=function(e){return(!this.str||this.str==="")&&this.modify(e),this.str},a.prototype.toFloat=function(){return this.modify(parseFloat(this.str)),this.str},a.prototype.toInt=function(e){return e=e||10,this.modify(parseInt(this.str,e)),this.str},a.prototype.toBoolean=function(){return!this.str||this.str=="0"||this.str=="false"||this.str==""?this.modify(!1):this.modify(!0),this.str},a.prototype.toBooleanStrict=function(){return this.str=="1"||this.str=="true"?this.modify(!0):this.modify(!1),this.str},e.sanitize=e.convert=function(t){var n=new e.Filter;return n.sanitize(t)},e.check=e.validate=e.assert=function(t,n){var r=new e.Validator;return r.check(t,n)},e}); diff --git a/validator.js b/validator.js index acf7c4229..62eee2a9e 100644 --- a/validator.js +++ b/validator.js @@ -335,208 +335,6 @@ decode: decode } - //This module is adapted from the CodeIgniter framework - //The license is available at http://codeigniter.com/ - - var never_allowed_str = { - 'document.cookie': '', - 'document.write': '', - '.parentNode': '', - '.innerHTML': '', - 'window.location': '', - '-moz-binding': '', - '<!--': '<!--', - '-->': '-->', - '<![CDATA[': '<![CDATA[' - }; - - var never_allowed_regex = { - 'javascript\\s*:': '', - 'expression\\s*(\\(|&\\#40;)': '', - 'vbscript\\s*:': '', - 'Redirect\\s+302': '' - }; - - var non_displayables = [ - /%0[0-8bcef]/g, // url encoded 00-08, 11, 12, 14, 15 - /%1[0-9a-f]/g, // url encoded 16-31 - /[\x00-\x08]/g, // 00-08 - /\x0b/g, /\x0c/g, // 11,12 - /[\x0e-\x1f]/g // 14-31 - ]; - - var compact_words = [ - 'javascript', 'expression', 'vbscript', - 'script', 'applet', 'alert', 'document', - 'write', 'cookie', 'window' - ]; - - exports.xssClean = function(str, is_image) { - - //Recursively clean objects and arrays - if (typeof str === 'object') { - for (var i in str) { - str[i] = exports.xssClean(str[i]); - } - return str; - } - - //Remove invisible characters - str = remove_invisible_characters(str); - - //Protect query string variables in URLs => 901119URL5918AMP18930PROTECT8198 - str = str.replace(/\&([a-z\_0-9]+)\=([a-z\_0-9]+)/i, xss_hash() + '$1=$2'); - - //Validate standard character entities - add a semicolon if missing. We do this to enable - //the conversion of entities to ASCII later. - str = str.replace(/(&\#?[0-9a-z]{2,})([\x00-\x20])*;?/i, '$1;$2'); - - //Validate UTF16 two byte encoding (x00) - just as above, adds a semicolon if missing. - str = str.replace(/(&\#x?)([0-9A-F]+);?/i, '$1;$2'); - - //Un-protect query string variables - str = str.replace(xss_hash(), '&'); - - //Decode just in case stuff like this is submitted: - //<a href="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a> - try { - str = decodeURIComponent(str); - } catch (e) { - // str was not actually URI-encoded - } - - //Convert character entities to ASCII - this permits our tests below to work reliably. - //We only convert entities that are within tags since these are the ones that will pose security problems. - str = str.replace(/[a-z]+=([\'\"]).*?\1/gi, function(m, match) { - return m.replace(match, convert_attribute(match)); - }); - - //Remove invisible characters again - str = remove_invisible_characters(str); - - //Convert tabs to spaces - str = str.replace('\t', ' '); - - //Captured the converted string for later comparison - var converted_string = str; - - //Remove strings that are never allowed - for (var i in never_allowed_str) { - str = str.replace(i, never_allowed_str[i]); - } - - //Remove regex patterns that are never allowed - for (var i in never_allowed_regex) { - str = str.replace(new RegExp(i, 'i'), never_allowed_regex[i]); - } - - //Compact any exploded words like: j a v a s c r i p t - // We only want to do this when it is followed by a non-word character - for (var i in compact_words) { - var spacified = compact_words[i].split('').join('\\s*')+'\\s*'; - - str = str.replace(new RegExp('('+spacified+')(\\W)', 'ig'), function(m, compat, after) { - return compat.replace(/\s+/g, '') + after; - }); - } - - //Remove disallowed Javascript in links or img tags - do { - var original = str; - - if (str.match(/<a/i)) { - str = str.replace(/<a\s+([^>]*?)(>|$)/gi, function(m, attributes, end_tag) { - attributes = filter_attributes(attributes.replace('<','').replace('>','')); - return m.replace(attributes, attributes.replace(/href=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)/gi, '')); - }); - } - - if (str.match(/<img/i)) { - str = str.replace(/<img\s+([^>]*?)(\s?\/?>|$)/gi, function(m, attributes, end_tag) { - attributes = filter_attributes(attributes.replace('<','').replace('>','')); - return m.replace(attributes, attributes.replace(/src=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)/gi, '')); - }); - } - - if (str.match(/script/i) || str.match(/xss/i)) { - str = str.replace(/<(\/*)(script|xss)(.*?)\>/gi, ''); - } - - } while(original != str); - - //Remove JavaScript Event Handlers - Note: This code is a little blunt. It removes the event - //handler and anything up to the closing >, but it's unlikely to be a problem. - event_handlers = ['[^a-z_\-]on\\w*']; - - //Adobe Photoshop puts XML metadata into JFIF images, including namespacing, - //so we have to allow this for images - if (!is_image) { - event_handlers.push('xmlns'); - } - - str = str.replace(new RegExp("<([^><]+?)("+event_handlers.join('|')+")(\\s*=\\s*[^><]*)([><]*)", 'i'), '<$1$4'); - - //Sanitize naughty HTML elements - //If a tag containing any of the words in the list - //below is found, the tag gets converted to entities. - //So this: <blink> - //Becomes: <blink> - naughty = 'alert|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|isindex|layer|link|meta|object|plaintext|style|script|textarea|title|video|xml|xss'; - str = str.replace(new RegExp('<(/*\\s*)('+naughty+')([^><]*)([><]*)', 'gi'), function(m, a, b, c, d) { - return '<' + a + b + c + d.replace('>','>').replace('<','<'); - }); - - //Sanitize naughty scripting elements Similar to above, only instead of looking for - //tags it looks for PHP and JavaScript commands that are disallowed. Rather than removing the - //code, it simply converts the parenthesis to entities rendering the code un-executable. - //For example: eval('some code') - //Becomes: eval('some code') - str = str.replace(/(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)/gi, '$1$2($3)'); - - //This adds a bit of extra precaution in case something got through the above filters - for (var i in never_allowed_str) { - str = str.replace(i, never_allowed_str[i]); - } - for (var i in never_allowed_regex) { - str = str.replace(new RegExp(i, 'i'), never_allowed_regex[i]); - } - - //Images are handled in a special way - if (is_image && str !== converted_string) { - throw new Error('Image may contain XSS'); - } - - return str; - } - - function remove_invisible_characters(str) { - for (var i in non_displayables) { - str = str.replace(non_displayables[i], ''); - } - return str; - } - - function xss_hash() { - //TODO: Create a random hash - return '!*$^#(@*#&'; - } - - function convert_attribute(str) { - return str.replace('>','>').replace('<','<').replace('\\','\\\\'); - } - - //Filter Attributes - filters tag attributes for consistency and safety - function filter_attributes(str) { - var comments = /\/\*.*?\*\//g; - return str.replace(/\s*[a-z-]+\s*=\s*'[^']*'/gi, function (m) { - return m.replace(comments, ''); - }).replace(/\s*[a-z-]+\s*=\s*"[^"]*"/gi, function (m) { - return m.replace(comments, ''); - }).replace(/\s*[a-z-]+\s*=\s*[^\s]+/gi, function (m) { - return m.replace(comments, ''); - }); - } - var Validator = exports.Validator = function() {} Validator.prototype.check = function(str, fail_msg) { @@ -913,11 +711,6 @@ return this; } - Filter.prototype.xss = function(is_image) { - this.modify(exports.xssClean(this.str, is_image)); - return this.str; - } - Filter.prototype.entityDecode = function() { this.modify(decode(this.str)); return this.str; @@ -1007,4 +800,4 @@ return exports; -})); \ No newline at end of file +}));