From bcb770d613dda528c01aa1d11924e6124f2619a1 Mon Sep 17 00:00:00 2001 From: Contra Date: Fri, 22 Jul 2011 01:32:58 -0700 Subject: [PATCH] initial commit. xss, lfi, csrf, and 404 so far --- LICENSE | 20 + README.md | 60 ++ fusker-log.txt | 722 +++++++++++++++ index.html | 13 + lib/config.js | 3 + lib/fusker.js | 87 ++ lib/mods/404.js | 21 + lib/mods/csrf.js | 17 + lib/mods/lfi.js | 7 + lib/mods/sqli.js | 0 lib/mods/xss.js | 11 + lib/util.js | 0 node_modules/validator/.gitignore | 1 + node_modules/validator/LICENSE | 20 + node_modules/validator/README.md | 169 ++++ node_modules/validator/index.html | 14 + node_modules/validator/index.js | 1 + node_modules/validator/lib/entities.js | 291 ++++++ node_modules/validator/lib/filter.js | 89 ++ node_modules/validator/lib/index.js | 15 + node_modules/validator/lib/validator.js | 230 +++++ node_modules/validator/lib/xss.js | 198 +++++ node_modules/validator/package.json | 27 + node_modules/validator/test.js | 4 + node_modules/validator/test/filter.test.js | 134 +++ node_modules/validator/test/validator.test.js | 454 ++++++++++ node_modules/validator/validator-min.js | 23 + node_modules/validator/validator.js | 838 ++++++++++++++++++ package.json | 45 + test.js | 17 + 30 files changed, 3531 insertions(+) create mode 100755 LICENSE create mode 100755 README.md create mode 100644 fusker-log.txt create mode 100644 index.html create mode 100644 lib/config.js create mode 100644 lib/fusker.js create mode 100644 lib/mods/404.js create mode 100644 lib/mods/csrf.js create mode 100644 lib/mods/lfi.js create mode 100644 lib/mods/sqli.js create mode 100644 lib/mods/xss.js create mode 100644 lib/util.js create mode 100644 node_modules/validator/.gitignore create mode 100755 node_modules/validator/LICENSE create mode 100755 node_modules/validator/README.md create mode 100644 node_modules/validator/index.html create mode 100755 node_modules/validator/index.js create mode 100755 node_modules/validator/lib/entities.js create mode 100755 node_modules/validator/lib/filter.js create mode 100755 node_modules/validator/lib/index.js create mode 100644 node_modules/validator/lib/validator.js create mode 100755 node_modules/validator/lib/xss.js create mode 100644 node_modules/validator/package.json create mode 100644 node_modules/validator/test.js create mode 100755 node_modules/validator/test/filter.test.js create mode 100644 node_modules/validator/test/validator.test.js create mode 100644 node_modules/validator/validator-min.js create mode 100644 node_modules/validator/validator.js create mode 100644 package.json create mode 100644 test.js diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..e987dc4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2011 Contra + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100755 index 0000000..45cf646 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +**node-fusker is a library to prevent and manage a multitude of attacks in nodejs** + +To install node-fusker, use [npm](http://github.com/isaacs/npm): + + $ npm install fusker + +## Example + +Please see [test.js](http://github.com/Contra/fusker/blob/master/test.js) for a working example and documentation + +## Levels of lulz (attacking the attackers) + + None - Blacklists IP + Low - Blacklists IP, Redirects to Google + Fair - Blacklists IP, Redirects to Saxroll + High - Blacklists IP, Redirects to browser attack page (high chance of crashing the attacker) + Extreme - Blacklists IP, Opens infinite tabs of attack pages (will absolutely crash the attacker) + +## List of included detection modules + + 404 - Detects 404 as a form of attack. Only use this if you have a single page application and want to punish people who like to snoop around + xss - Detects common XSS attacks in incoming http requests + sqli - Detects SQLi attempts in incoming http requests. Only an idiot would try to SQLi a node server (lol) + lfi - Detects common LFI attacks in incoming http requests + +## Extending the library + +Add a .js or .coffee file to the 'mods' directory of the module. + +Example: + Adding msi.js to /mods/ will allow you to fusker.detect.push('msi'); + +## Contributors + +- [tprime](https://github.com/tprime) - General + +## LICENSE + +(MIT License) + +Copyright (c) 2011 Contra + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/fusker-log.txt b/fusker-log.txt new file mode 100644 index 0000000..bff27d5 --- /dev/null +++ b/fusker-log.txt @@ -0,0 +1,722 @@ +[ - ATTACK DETAILS FOR function now() { [native code] } - ] + --> Module: CSRF + --> Request: GET /index.html?wat=1%22%3E%3Cimg%20src=x%20onerror=alert%28%22XSS%22%29%3E%3Cnoscript%3E + --> IP: 127.0.0.1 + --> Raw Request: { bufferSize: 0, + fd: 7, + type: 'tcp4', + allowHalfOpen: true, + _readWatcher: + { socket: [Circular], + callback: [Function: onReadable] }, + destroyed: false, + readable: true, + _writeQueue: [], + _writeQueueEncoding: [], + _writeQueueFD: [], + _writeQueueCallbacks: [], + _writeWatcher: + { socket: [Circular], + callback: [Function: onWritable] }, + writable: true, + _writeImpl: [Function], + _readImpl: [Function], + _shutdownImpl: [Function], + remoteAddress: '127.0.0.1', + remotePort: 43438, + server: + { connections: 1, + allowHalfOpen: true, + watcher: { host: [Circular], callback: [Function] }, + _events: + { request: [Function], + connection: [Function: connectionListener] }, + httpAllowHalfOpen: false, + type: 'tcp4', + fd: 5 }, + ondrain: [Function], + _idleTimeout: 120000, + _idleNext: + { repeat: 120, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idlePrev: + { repeat: 120, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idleStart: Fri, 22 Jul 2011 08:09:46 GMT, + _events: + { timeout: [Function], + error: [Function], + close: [Function] }, + ondata: [Function], + onend: [Function], + _httpMessage: + { output: [], + outputEncodings: [], + writable: true, + _last: false, + chunkedEncoding: false, + shouldKeepAlive: true, + useChunkedEncodingByDefault: true, + _hasBody: true, + _trailer: '', + finished: false, + socket: [Circular], + connection: [Circular], + _events: { finish: [Function] } } } +[ - END ATTACK DETAILS - ] + +[ - ATTACK DETAILS FOR function now() { [native code] } - ] + --> Module: CSRF + --> Request: GET /favicon.ico + --> IP: 127.0.0.1 + --> Raw Request: { bufferSize: 0, + fd: 7, + type: 'tcp4', + allowHalfOpen: true, + _readWatcher: + { socket: [Circular], + callback: [Function: onReadable] }, + destroyed: false, + readable: true, + _writeQueue: [], + _writeQueueEncoding: [], + _writeQueueFD: [], + _writeQueueCallbacks: [], + _writeWatcher: + { socket: [Circular], + callback: [Function: onWritable] }, + writable: true, + _writeImpl: [Function], + _readImpl: [Function], + _shutdownImpl: [Function], + remoteAddress: '127.0.0.1', + remotePort: 43438, + server: + { connections: 1, + allowHalfOpen: true, + watcher: { host: [Circular], callback: [Function] }, + _events: + { request: [Function], + connection: [Function: connectionListener] }, + httpAllowHalfOpen: false, + type: 'tcp4', + fd: 5 }, + ondrain: [Function], + _idleTimeout: 120000, + _idleNext: + { repeat: 120, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idlePrev: + { repeat: 120, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idleStart: Fri, 22 Jul 2011 08:09:46 GMT, + _events: + { timeout: [Function], + error: [Function], + close: [Function] }, + ondata: [Function], + onend: [Function], + _httpMessage: + { output: [], + outputEncodings: [], + writable: true, + _last: false, + chunkedEncoding: false, + shouldKeepAlive: true, + useChunkedEncodingByDefault: true, + _hasBody: true, + _trailer: '', + finished: false, + socket: [Circular], + connection: [Circular], + _events: { finish: [Function] } } } +[ - END ATTACK DETAILS - ] + +[ - ATTACK DETAILS FOR function now() { [native code] } - ] + --> Module: CSRF + --> Request: GET /index.html?wat=1%22%3E%3Cimg%20src=x%20onerror=alert%28%22XSS%22%29%3E%3Cnoscript%3E + --> IP: 127.0.0.1 + --> Raw Request: { bufferSize: 0, + fd: 8, + type: 'tcp4', + allowHalfOpen: true, + _readWatcher: + { socket: [Circular], + callback: [Function: onReadable] }, + destroyed: false, + readable: true, + _writeQueue: [], + _writeQueueEncoding: [], + _writeQueueFD: [], + _writeQueueCallbacks: [], + _writeWatcher: + { socket: [Circular], + callback: [Function: onWritable] }, + writable: true, + _writeImpl: [Function], + _readImpl: [Function], + _shutdownImpl: [Function], + remoteAddress: '127.0.0.1', + remotePort: 43448, + server: + { connections: 2, + allowHalfOpen: true, + watcher: { host: [Circular], callback: [Function] }, + _events: + { request: [Function], + connection: [Function: connectionListener] }, + httpAllowHalfOpen: false, + type: 'tcp4', + fd: 5 }, + ondrain: [Function], + _idleTimeout: 120000, + _idleNext: + { bufferSize: 0, + fd: 7, + type: 'tcp4', + allowHalfOpen: true, + _readWatcher: + { socket: [Circular], + callback: [Function: onReadable] }, + destroyed: false, + readable: true, + _writeQueue: [], + _writeQueueEncoding: [], + _writeQueueFD: [], + _writeQueueCallbacks: [], + _writeWatcher: + { socket: [Circular], + callback: [Function: onWritable] }, + writable: true, + _writeImpl: [Function], + _readImpl: [Function], + _shutdownImpl: [Function], + remoteAddress: '127.0.0.1', + remotePort: 43438, + server: + { connections: 2, + allowHalfOpen: true, + watcher: [Object], + _events: [Object], + httpAllowHalfOpen: false, + type: 'tcp4', + fd: 5 }, + ondrain: [Function], + _idleTimeout: 120000, + _idleNext: + { repeat: 120, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idlePrev: [Circular], + _idleStart: Fri, 22 Jul 2011 08:09:46 GMT, + _events: + { timeout: [Function], + error: [Function], + close: [Function] }, + ondata: [Function], + onend: [Function], + _httpMessage: + { output: [], + outputEncodings: [], + writable: true, + _last: false, + chunkedEncoding: false, + shouldKeepAlive: true, + useChunkedEncodingByDefault: true, + _hasBody: true, + _trailer: '', + finished: false, + socket: [Circular], + connection: [Circular], + _events: [Object] } }, + _idlePrev: + { repeat: 120, + _idleNext: [Circular], + _idlePrev: + { bufferSize: 0, + fd: 7, + type: 'tcp4', + allowHalfOpen: true, + _readWatcher: [Object], + destroyed: false, + readable: true, + _writeQueue: [], + _writeQueueEncoding: [], + _writeQueueFD: [], + _writeQueueCallbacks: [], + _writeWatcher: [Object], + writable: true, + _writeImpl: [Function], + _readImpl: [Function], + _shutdownImpl: [Function], + remoteAddress: '127.0.0.1', + remotePort: 43438, + server: [Object], + ondrain: [Function], + _idleTimeout: 120000, + _idleNext: [Circular], + _idlePrev: [Circular], + _idleStart: Fri, 22 Jul 2011 08:09:46 GMT, + _events: [Object], + ondata: [Function], + onend: [Function], + _httpMessage: [Object] }, + callback: [Function] }, + _idleStart: Fri, 22 Jul 2011 08:09:53 GMT, + _events: + { timeout: [Function], + error: [Function], + close: [Function] }, + ondata: [Function], + onend: [Function], + _httpMessage: + { output: [], + outputEncodings: [], + writable: true, + _last: false, + chunkedEncoding: false, + shouldKeepAlive: true, + useChunkedEncodingByDefault: true, + _hasBody: true, + _trailer: '', + finished: false, + socket: [Circular], + connection: [Circular], + _events: { finish: [Function] } } } +[ - END ATTACK DETAILS - ] + +[ - ATTACK DETAILS FOR function now() { [native code] } - ] + --> Module: CSRF + --> Request: GET /favicon.ico + --> IP: 127.0.0.1 + --> Raw Request: { bufferSize: 0, + fd: 8, + type: 'tcp4', + allowHalfOpen: true, + _readWatcher: + { socket: [Circular], + callback: [Function: onReadable] }, + destroyed: false, + readable: true, + _writeQueue: [], + _writeQueueEncoding: [], + _writeQueueFD: [], + _writeQueueCallbacks: [], + _writeWatcher: + { socket: [Circular], + callback: [Function: onWritable] }, + writable: true, + _writeImpl: [Function], + _readImpl: [Function], + _shutdownImpl: [Function], + remoteAddress: '127.0.0.1', + remotePort: 43448, + server: + { connections: 1, + allowHalfOpen: true, + watcher: { host: [Circular], callback: [Function] }, + _events: + { request: [Function], + connection: [Function: connectionListener] }, + httpAllowHalfOpen: false, + type: 'tcp4', + fd: 5 }, + ondrain: [Function], + _idleTimeout: 120000, + _idleNext: + { repeat: 7, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idlePrev: + { repeat: 7, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idleStart: Fri, 22 Jul 2011 08:11:46 GMT, + _events: + { timeout: [Function], + error: [Function], + close: [Function] }, + ondata: [Function], + onend: [Function], + _httpMessage: + { output: [], + outputEncodings: [], + writable: true, + _last: false, + chunkedEncoding: false, + shouldKeepAlive: true, + useChunkedEncodingByDefault: true, + _hasBody: true, + _trailer: '', + finished: false, + socket: [Circular], + connection: [Circular], + _events: { finish: [Function] } } } +[ - END ATTACK DETAILS - ] + +[ - ATTACK DETAILS FOR function now() { [native code] } - ] + --> Module: CSRF + --> Request: GET /favicon.ico + --> IP: 127.0.0.1 + --> Raw Request: { bufferSize: 0, + fd: 7, + type: 'tcp4', + allowHalfOpen: true, + _readWatcher: + { socket: [Circular], + callback: [Function: onReadable] }, + destroyed: false, + readable: true, + _writeQueue: [], + _writeQueueEncoding: [], + _writeQueueFD: [], + _writeQueueCallbacks: [], + _writeWatcher: + { socket: [Circular], + callback: [Function: onWritable] }, + writable: true, + _writeImpl: [Function], + _readImpl: [Function], + _shutdownImpl: [Function], + remoteAddress: '127.0.0.1', + remotePort: 34537, + server: + { connections: 1, + allowHalfOpen: true, + watcher: { host: [Circular], callback: [Function] }, + _events: + { request: [Function], + connection: [Function: connectionListener] }, + httpAllowHalfOpen: false, + type: 'tcp4', + fd: 5 }, + ondrain: [Function], + _idleTimeout: 120000, + _idleNext: + { repeat: 120, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idlePrev: + { repeat: 120, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idleStart: Fri, 22 Jul 2011 08:13:46 GMT, + _events: + { timeout: [Function], + error: [Function], + close: [Function] }, + ondata: [Function], + onend: [Function], + _httpMessage: + { output: [], + outputEncodings: [], + writable: true, + _last: false, + chunkedEncoding: false, + shouldKeepAlive: true, + useChunkedEncodingByDefault: true, + _hasBody: true, + _trailer: '', + finished: false, + socket: [Circular], + connection: [Circular], + _events: { finish: [Function] } } } +[ - END ATTACK DETAILS - ] + +[ - ATTACK DETAILS FOR function now() { [native code] } - ] + --> Module: CSRF-1 + --> Request: GET /index.html?wat=1%22%3E%3Cimg%20src=x%20onerror=alert%28%22XSS%22%29%3E%3Cnoscript%3E + --> IP: 127.0.0.1 + --> Raw Request: { bufferSize: 0, + fd: 7, + type: 'tcp4', + allowHalfOpen: true, + _readWatcher: + { socket: [Circular], + callback: [Function: onReadable] }, + destroyed: false, + readable: true, + _writeQueue: [], + _writeQueueEncoding: [], + _writeQueueFD: [], + _writeQueueCallbacks: [], + _writeWatcher: + { socket: [Circular], + callback: [Function: onWritable] }, + writable: true, + _writeImpl: [Function], + _readImpl: [Function], + _shutdownImpl: [Function], + remoteAddress: '127.0.0.1', + remotePort: 34558, + server: + { connections: 1, + allowHalfOpen: true, + watcher: { host: [Circular], callback: [Function] }, + _events: + { request: [Function], + connection: [Function: connectionListener] }, + httpAllowHalfOpen: false, + type: 'tcp4', + fd: 5 }, + ondrain: [Function], + _idleTimeout: 120000, + _idleNext: + { repeat: 120, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idlePrev: + { repeat: 120, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idleStart: Fri, 22 Jul 2011 08:15:26 GMT, + _events: + { timeout: [Function], + error: [Function], + close: [Function] }, + ondata: [Function], + onend: [Function], + _httpMessage: + { output: [], + outputEncodings: [], + writable: true, + _last: false, + chunkedEncoding: false, + shouldKeepAlive: true, + useChunkedEncodingByDefault: true, + _hasBody: true, + _trailer: '', + finished: false, + socket: [Circular], + connection: [Circular], + _events: { finish: [Function] } } } +[ - END ATTACK DETAILS - ] + +[ - ATTACK DETAILS FOR function now() { [native code] } - ] + --> Module: CSRF-1 + --> Request: GET /favicon.ico + --> IP: 127.0.0.1 + --> Raw Request: { bufferSize: 0, + fd: 7, + type: 'tcp4', + allowHalfOpen: true, + _readWatcher: + { socket: [Circular], + callback: [Function: onReadable] }, + destroyed: false, + readable: true, + _writeQueue: [], + _writeQueueEncoding: [], + _writeQueueFD: [], + _writeQueueCallbacks: [], + _writeWatcher: + { socket: [Circular], + callback: [Function: onWritable] }, + writable: true, + _writeImpl: [Function], + _readImpl: [Function], + _shutdownImpl: [Function], + remoteAddress: '127.0.0.1', + remotePort: 34558, + server: + { connections: 1, + allowHalfOpen: true, + watcher: { host: [Circular], callback: [Function] }, + _events: + { request: [Function], + connection: [Function: connectionListener] }, + httpAllowHalfOpen: false, + type: 'tcp4', + fd: 5 }, + ondrain: [Function], + _idleTimeout: 120000, + _idleNext: + { repeat: 120, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idlePrev: + { repeat: 120, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idleStart: Fri, 22 Jul 2011 08:15:26 GMT, + _events: + { timeout: [Function], + error: [Function], + close: [Function] }, + ondata: [Function], + onend: [Function], + _httpMessage: + { output: [], + outputEncodings: [], + writable: true, + _last: false, + chunkedEncoding: false, + shouldKeepAlive: true, + useChunkedEncodingByDefault: true, + _hasBody: true, + _trailer: '', + finished: false, + socket: [Circular], + connection: [Circular], + _events: { finish: [Function] } } } +[ - END ATTACK DETAILS - ] + +[ - ATTACK DETAILS FOR function now() { [native code] } - ] + --> Module: CSRF-1 + --> Request: GET /favicon.ico + --> IP: 127.0.0.1 + --> Raw Request: { bufferSize: 0, + fd: 7, + type: 'tcp4', + allowHalfOpen: true, + _readWatcher: + { socket: [Circular], + callback: [Function: onReadable] }, + destroyed: false, + readable: true, + _writeQueue: [], + _writeQueueEncoding: [], + _writeQueueFD: [], + _writeQueueCallbacks: [], + _writeWatcher: + { socket: [Circular], + callback: [Function: onWritable] }, + writable: true, + _writeImpl: [Function], + _readImpl: [Function], + _shutdownImpl: [Function], + remoteAddress: '127.0.0.1', + remotePort: 54112, + server: + { connections: 1, + allowHalfOpen: true, + watcher: { host: [Circular], callback: [Function] }, + _events: + { request: [Function], + connection: [Function: connectionListener] }, + httpAllowHalfOpen: false, + type: 'tcp4', + fd: 5 }, + ondrain: [Function], + _idleTimeout: 120000, + _idleNext: + { repeat: 120, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idlePrev: + { repeat: 120, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idleStart: Fri, 22 Jul 2011 08:17:26 GMT, + _events: + { timeout: [Function], + error: [Function], + close: [Function] }, + ondata: [Function], + onend: [Function], + _httpMessage: + { output: [], + outputEncodings: [], + writable: true, + _last: false, + chunkedEncoding: false, + shouldKeepAlive: true, + useChunkedEncodingByDefault: true, + _hasBody: true, + _trailer: '', + finished: false, + socket: [Circular], + connection: [Circular], + _events: { finish: [Function] } } } +[ - END ATTACK DETAILS - ] + +[ - ATTACK DETAILS FOR function now() { [native code] } - ] + --> Module: XSS + --> Request: GET /index.html?wat=1%22%3E%3Cimg+src%3Dx+onerror%3Dalert%28%22XSS%22%29%3E%3Cnoscript%3E + --> IP: 127.0.0.1 + --> Raw Request: { bufferSize: 0, + fd: 7, + type: 'tcp4', + allowHalfOpen: true, + _readWatcher: + { socket: [Circular], + callback: [Function: onReadable] }, + destroyed: false, + readable: true, + _writeQueue: [], + _writeQueueEncoding: [], + _writeQueueFD: [], + _writeQueueCallbacks: [], + _writeWatcher: + { socket: [Circular], + callback: [Function: onWritable] }, + writable: true, + _writeImpl: [Function], + _readImpl: [Function], + _shutdownImpl: [Function], + remoteAddress: '127.0.0.1', + remotePort: 55205, + server: + { connections: 1, + allowHalfOpen: true, + watcher: { host: [Circular], callback: [Function] }, + _events: + { request: [Function], + connection: [Function: connectionListener] }, + httpAllowHalfOpen: false, + type: 'tcp4', + fd: 5 }, + ondrain: [Function], + _idleTimeout: 120000, + _idleNext: + { repeat: 120, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idlePrev: + { repeat: 120, + _idleNext: [Circular], + _idlePrev: [Circular], + callback: [Function] }, + _idleStart: Fri, 22 Jul 2011 08:32:18 GMT, + _events: + { timeout: [Function], + error: [Function], + close: [Function] }, + ondata: [Function], + onend: [Function], + _httpMessage: + { output: [], + outputEncodings: [], + writable: true, + _last: false, + chunkedEncoding: false, + shouldKeepAlive: true, + useChunkedEncodingByDefault: true, + _hasBody: true, + _trailer: '', + finished: false, + socket: [Circular], + connection: [Circular], + _events: { finish: [Function] } } } +[ - END ATTACK DETAILS - ] + diff --git a/index.html b/index.html new file mode 100644 index 0000000..aeaf2c5 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + +Fusker - Test Page + + +

+
+

Fusker Test Page


+ + +
+ + diff --git a/lib/config.js b/lib/config.js new file mode 100644 index 0000000..b3f1d18 --- /dev/null +++ b/lib/config.js @@ -0,0 +1,3 @@ +exports.dir = process.cwd(); +exports.level = 'FAIR'; +exports.banlength = 1440; diff --git a/lib/fusker.js b/lib/fusker.js new file mode 100644 index 0000000..f81bee1 --- /dev/null +++ b/lib/fusker.js @@ -0,0 +1,87 @@ +var http = require('http'); +var url = require('url'); +var sys = require('sys'); +var fs = require('fs'); +var path = require('path'); + +/* Create global fusker var */ +global.fusker = {}; + +/* Create our different levels of lulz */ +fusker.levels = {}; +fusker.levels.NONE = 'NONE'; +fusker.levels.LOW = 'LOW'; +fusker.levels.FAIR = 'FAIR'; +fusker.levels.HIGH = 'HIGH'; +fusker.levels.EXTREME = 'EXTREME'; + +/* Bring in any internal library files */ +fusker.util = require('./util'); +fusker.config = require('./config'); +fusker.detect = new Array(); + +/* Creates http server on specified port and listens on it. Runs every request through modules */ +fusker.createServer = function (port) { + console.log('[FUSKER] Creating server on port ' + port); + console.log('[FUSKER] Modules: ' + fusker.detect); + var serv = http.createServer(function (req, res) { + + //Fix file names + var file = url.parse(req.url).pathname; + if (file == '/') { + file = '/index.html'; + } + console.log('[FUSKER] ' + req.connection.remoteAddress + ' -> ' + file); + + //Run request through specified modules + for (var i = fusker.detect.length - 1; i >= 0; --i) { + var module = require('./mods/' + fusker.detect[i]); + module.check(req, res); + } + + //Feed up the file + fs.readFile(fusker.config.dir + file, function (err, data) { + if (!err) { + res.writeHead(200); + res.write(data, 'utf8'); + res.end(); + } + }); + }); + serv.listen(port); + return serv; +}; + +/* Appends attack details to specified log file */ +fusker.logAttack = function (file, module, req) { + var log = fs.createWriteStream(file, { + 'flags': 'a' + }); + + log.write('[ - ATTACK DETAILS FOR ' + Date.now + ' - ]\r\n'); + log.write(' --> Module: ' + module + '\r\n'); + log.write(' --> Request: ' + req.method + ' ' + req.url + '\r\n'); + log.write(' --> IP: ' + req.connection.remoteAddress + '\r\n'); + log.write(' --> Raw Request: ' + sys.inspect(req.connection) + '\r\n'); + log.write('[ - END ATTACK DETAILS - ]\r\n\r\n'); + log.end(); +}; + +/* Executes payload on attacker based on lulz level */ +fusker.handleAttack = function (module, req, res) { + var ip = req.connection.remoteAddress; + console.log('[FUSKER] Attack detected! Module: ' + module + ' IP: ' + ip); + fusker.logAttack('fusker-log.txt', module, req); + switch (fusker.config.level.toUpperCase()) { + case 'NONE': + break; + case 'LOW': + break; + case 'FAIR': + break; + case 'HIGH': + break; + case 'EXTREME': + break; + } +}; diff --git a/lib/mods/404.js b/lib/mods/404.js new file mode 100644 index 0000000..49a61cc --- /dev/null +++ b/lib/mods/404.js @@ -0,0 +1,21 @@ +var fs = require('fs'); +var url = require('url'); + +exports.check = function (req, res) { + var file = url.parse(req.url).pathname; + switch (file) { + case '/': + file = '/index.html'; + break; + + case '/favicon.ico': + //exit out if its the favicon, no need to 404 it + return; + } + + fs.readFile(fusker.config.dir + file, function (err, data) { + if (err) { + fusker.handleAttack('404', req, res); + } + }); +}; \ No newline at end of file diff --git a/lib/mods/csrf.js b/lib/mods/csrf.js new file mode 100644 index 0000000..db586b9 --- /dev/null +++ b/lib/mods/csrf.js @@ -0,0 +1,17 @@ +exports.check = function (req, res) { + var headers = req.headers; + /* + if (!headers['x-requested-with']) { + fusker.handleAttack('CSRF-1', req, res); + + } else */ + if (/application\/j/.test(headers.accept)) { + fusker.handleAttack('CSRF-2', req, res); + + } else if (req.method == "POST" && headers.referer && headers.referer.indexOf(headers.host + '/') > 0) { + fusker.handleAttack('CSRF-3', req, res); + + } else if (req.method != "GET" && req.method != "POST") { + fusker.handleAttack('CSRF-4', req, res); + } +}; \ No newline at end of file diff --git a/lib/mods/lfi.js b/lib/mods/lfi.js new file mode 100644 index 0000000..0ea4cc7 --- /dev/null +++ b/lib/mods/lfi.js @@ -0,0 +1,7 @@ +var url = require('url'); + +exports.check = function (req, res) { + if (req.url.indexOf('../') > -1) { + fusker.handleAttack('LFI', req, res); + } +}; \ No newline at end of file diff --git a/lib/mods/sqli.js b/lib/mods/sqli.js new file mode 100644 index 0000000..e69de29 diff --git a/lib/mods/xss.js b/lib/mods/xss.js new file mode 100644 index 0000000..e5db776 --- /dev/null +++ b/lib/mods/xss.js @@ -0,0 +1,11 @@ +exports.check = function (req, res) { + var original = unescape(unescape(req.url)); + var xss = ['<', '>', '(', ')']; + + for (var i = xss.length - 1; i >= 0; --i) { + if (original.indexOf(xss[i]) > -1) { + fusker.handleAttack('XSS', req, res); + return; + } + } +}; \ No newline at end of file diff --git a/lib/util.js b/lib/util.js new file mode 100644 index 0000000..e69de29 diff --git a/node_modules/validator/.gitignore b/node_modules/validator/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/node_modules/validator/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/node_modules/validator/LICENSE b/node_modules/validator/LICENSE new file mode 100755 index 0000000..c1a825a --- /dev/null +++ b/node_modules/validator/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2010 Chris O'Hara + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/validator/README.md b/node_modules/validator/README.md new file mode 100755 index 0000000..dea18fe --- /dev/null +++ b/node_modules/validator/README.md @@ -0,0 +1,169 @@ +**node-validator is a library of string validation, filtering and sanitization methods.** + +To install node-validator, use [npm](http://github.com/isaacs/npm): + + $ npm install validator + +To use the library in the browser, include `validator-min.js` + +## Example + + var check = require('validator').check, + sanitize = require('validator').sanitize + + //Validate + check('test@email.com').len(6, 64).isEmail(); //Methods are chainable + check('abc').isInt(); //Throws 'Invalid integer' + check('abc', 'Please enter a number').isInt(); //Throws 'Please enter a number' + check('abcdefghijklmnopzrtsuvqxyz').is(/^[a-z]+$/); + + //Sanitize / Filter + var int = sanitize('0123').toInt(); //123 + var bool = sanitize('true').toBoolean(); //true + var str = sanitize(' \s\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(); //'' + +## Web development + +Often it's more desirable to check or automatically sanitize parameters by name (rather than the actual string). See [this gist](https://gist.github.com/752126) for instructions on binding the library to the `request` prototype. + +Example `http://localhost:8080/?zip=12345&foo=1&textarea=large_string` + + get('/', function (req, res) { + req.onValidationError(function (msg) { + //Redirect the user with error 'msg' + }); + + //Validate user input + req.check('zip', 'Please enter a valid ZIP code').len(4,5).isInt(); + req.check('email', 'Please enter a valid email').len(6,64).isEmail(); + req.checkHeader('referer').contains('localhost'); + + //Sanitize user input + req.sanitize('textarea').xss(); + req.sanitize('foo').toBoolean(); + + //etc. + }); + +## List of validation methods + + is() //Alias for regex() + not() //Alias for notRegex() + isEmail() + isUrl() //Accepts http, https, ftp + isIP() + isAlpha() + isAlphanumeric() + isNumeric() + isInt() //isNumeric accepts zero padded numbers, e.g. '001', isInt doesn't + isLowercase() + isUppercase() + isDecimal() + isFloat() //Alias for isDecimal + notNull() + isNull() + notEmpty() //i.e. not just whitespace + equals(equals) + contains(str) + notContains(str) + regex(pattern, modifiers) //Usage: regex(/[a-z]/i) or regex('[a-z]','i') + notRegex(pattern, modifiers) + len(min, max) //max is optional + isDate(date) + in(options) //Accepts an array or string + notIn(options) + +## List of sanitization / filter methods + + trim(chars) //Trim optional `chars`, default is to trim whitespace (\r\n\t\s) + ltrim(chars) + rtrim(chars) + ifNull(replace) + toFloat() + toInt() + toBoolean() //True unless str = '0', 'false', or str.length == 0 + toBooleanStrict() //False unless str = '1' or 'true' + entityDecode() //Decode HTML entities + entityEncode() + xss() //Remove common XSS attack vectors from text (default) + xss(true) //Remove common XSS attack vectors from images + +## Extending the library + +When adding to the Validator prototype, use `this.str` to access the string and `this.error(this.msg || default_msg)` when the string is invalid + + var Validator = require('validator').Validator; + Validator.prototype.contains = function(str) { + if (!~this.str.indexOf(str)) { + this.error(this.msg || this.str + ' does not contain ' + str); + } + return this; //Allow method chaining + } + +When adding to the Filter (sanitize) prototype, use `this.str` to access the string and `this.modify(new_str)` to update it + + var Filter = require('filter').Filter; + Filter.prototype.removeNumbers = function() { + this.modify(this.str.replace(/[0-9]+/g, '')); + return this.str; + } + +## Error handling + +By default, the validation methods throw an exception when a check fails + + try { + check('abc').notNull().isInt() + } catch (e) { + console.log(e.message); //Invalid integer + } + +To set a custom error message, set the second param of `check()` + + try { + check('abc', 'Please enter a valid integer').notNull().isInt() + } catch (e) { + console.log(e.message); //Please enter a valid integer + } + +To attach a custom error handler, set the `error` method of the validator instance + + var Validator = require('validator').Validator; + var v = new Validator(); + v.error = function(msg) { + console.log('Fail'); + } + v.check('abc').isInt(); //'Fail' + +## Contributors + +- [PING](https://github.com/PlNG) - Fixed entity encoding +- [Dan VerWeire](https://github.com/wankdanker) - Modified the behaviour of the error handler + +## LICENSE + +(MIT License) + +Copyright (c) 2010 Chris O'Hara + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/validator/index.html b/node_modules/validator/index.html new file mode 100644 index 0000000..d6c0bf8 --- /dev/null +++ b/node_modules/validator/index.html @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/node_modules/validator/index.js b/node_modules/validator/index.js new file mode 100755 index 0000000..0f485b9 --- /dev/null +++ b/node_modules/validator/index.js @@ -0,0 +1 @@ +exports = module.exports = require('./lib'); diff --git a/node_modules/validator/lib/entities.js b/node_modules/validator/lib/entities.js new file mode 100755 index 0000000..ed80911 --- /dev/null +++ b/node_modules/validator/lib/entities.js @@ -0,0 +1,291 @@ +var entities = { + ' ': '\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', + '"': '\u0022', + '<': '\u003c', + '>': '\u003e', + ''': '\u0027', + '−': '\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' +}; + +exports.decode = function (str) { + if (!~str.indexOf('&')) return str; + + //Decode literal entities + for (var i in entities) { + str = str.replace(new RegExp(i, 'g'), entities[i]); + } + + //Decode hex entities + str = str.replace(/&#x(0*[0-9a-f]{2,5});?/gi, function (m, code) { + return String.fromCharCode(parseInt(+code, 16)); + }); + + //Decode numeric entities + str = str.replace(/&#([0-9]{2,4});?/gi, function (m, code) { + return String.fromCharCode(+code); + }); + + str = str.replace(/&/g, '&'); + + return str; +} + +exports.encode = function (str) { + str = str.replace(/&/g, '&'); + + //IE doesn't accept ' + str = str.replace(/'/g, '''); + + //Encode literal entities + for (var i in entities) { + str = str.replace(new RegExp(entities[i], 'g'), i); + } + + return str; +} diff --git a/node_modules/validator/lib/filter.js b/node_modules/validator/lib/filter.js new file mode 100755 index 0000000..dd718e9 --- /dev/null +++ b/node_modules/validator/lib/filter.js @@ -0,0 +1,89 @@ +var entities = require('./entities'); + xss = require('./xss'); + +var Filter = exports.Filter = function() {} + +var whitespace = '\\r\\n\\t\\s'; + +Filter.prototype.modify = function(str) { + this.str = str; +} + +//Create some aliases - may help code readability +Filter.prototype.convert = Filter.prototype.sanitize = function(str) { + this.str = str; + return this; +} + +Filter.prototype.xss = function(is_image) { + this.modify(xss.clean(this.str, is_image)); + return this.str; +} + +Filter.prototype.entityDecode = function() { + this.modify(entities.decode(this.str)); + return this.str; +} + +Filter.prototype.entityEncode = function() { + this.modify(entities.encode(this.str)); + return this.str; +} + +Filter.prototype.ltrim = function(chars) { + chars = chars || whitespace; + this.modify(this.str.replace(new RegExp('^['+chars+']+', 'g'), '')); + return this.str; +} + +Filter.prototype.rtrim = function(chars) { + chars = chars || whitespace; + this.modify(this.str.replace(new RegExp('['+chars+']+$', 'g'), '')); + return this.str; +} + +Filter.prototype.trim = function(chars) { + chars = chars || whitespace; + this.modify(this.str.replace(new RegExp('^['+chars+']+|['+chars+']+$', 'g'), '')); + return this.str; +} + +Filter.prototype.ifNull = function(replace) { + if (!this.str || this.str === '') { + this.modify(replace); + } + return this.str; +} + +Filter.prototype.toFloat = function() { + this.modify(parseFloat(this.str)); + return this.str; +} + +Filter.prototype.toInt = function(radix) { + radix = radix || 10; + this.modify(parseInt(this.str, radix)); + return this.str; +} + +//Any strings with length > 0 (except for '0' and 'false') are considered true, +//all other strings are false +Filter.prototype.toBoolean = function() { + if (!this.str || this.str == '0' || this.str == 'false' || this.str == '') { + this.modify(false); + } else { + this.modify(true); + } + return this.str; +} + +//String must be equal to '1' or 'true' to be considered true, all other strings +//are false +Filter.prototype.toBooleanStrict = function() { + if (this.str == '1' || this.str == 'true') { + this.modify(true); + } else { + this.modify(false); + } + return this.str; +} diff --git a/node_modules/validator/lib/index.js b/node_modules/validator/lib/index.js new file mode 100755 index 0000000..e03ffbb --- /dev/null +++ b/node_modules/validator/lib/index.js @@ -0,0 +1,15 @@ +exports.Validator = require('./validator').Validator; +exports.Filter = require('./filter').Filter; + +exports.entities = require('./entities'); + +//Quick access methods +exports.sanitize = exports.convert = function(str) { + var filter = new exports.Filter(); + return filter.sanitize(str); +} + +exports.check = exports.validate = exports.assert = function(str, fail_msg) { + var validator = new exports.Validator(); + return validator.check(str, fail_msg); +} \ No newline at end of file diff --git a/node_modules/validator/lib/validator.js b/node_modules/validator/lib/validator.js new file mode 100644 index 0000000..8bcc532 --- /dev/null +++ b/node_modules/validator/lib/validator.js @@ -0,0 +1,230 @@ +var net = require('net'); + +var Validator = exports.Validator = function() {} + +Validator.prototype.check = function(str, fail_msg) { + this.str = str == null || (isNaN(str) && str.length == undefined) ? '' : str+''; + this.msg = fail_msg; + this._errors = []; + return this; +} + +//Create some aliases - may help code readability +Validator.prototype.validate = Validator.prototype.check; +Validator.prototype.assert = Validator.prototype.check; + +Validator.prototype.error = function(msg) { + throw new Error(msg); + return this; +} + +Validator.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; +} + +Validator.prototype.isUrl = function() { + if (!this.str.match(/^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?((?:(?:[-\w\d{1-3}]+\.)+(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|edu|co\.uk|ac\.uk|it|fr|tv|museum|asia|local|travel|[a-z]{2}))|((\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)(\.(\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)){3}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$ |\/.,*:;=]|%[a-f\d]{2})*)?$/)) { + return this.error(this.msg || 'Invalid URL'); + } + return this; +} + +Validator.prototype.isIP = function() { + //net.isIP is in node >= 0.3.0 + if (typeof net.isIP === 'function') { + if (net.isIP(this.str) === 0) { + return this.error(this.msg || 'Invalid IP'); + } + } else { + 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; +} + +Validator.prototype.isAlpha = function() { + if (!this.str.match(/^[a-zA-Z]+$/)) { + return this.error(this.msg || 'Invalid characters'); + } + return this; +} + +Validator.prototype.isAlphanumeric = function() { + if (!this.str.match(/^[a-zA-Z0-9]+$/)) { + return this.error(this.msg || 'Invalid characters'); + } + return this; +} + +Validator.prototype.isNumeric = function() { + if (!this.str.match(/^-?[0-9]+$/)) { + return this.error(this.msg || 'Invalid number'); + } + return this; +} + +Validator.prototype.isLowercase = function() { + if (!this.str.match(/^[a-z0-9]+$/)) { + return this.error(this.msg || 'Invalid characters'); + } + return this; +} + +Validator.prototype.isUppercase = function() { + if (!this.str.match(/^[A-Z0-9]+$/)) { + return this.error(this.msg || 'Invalid characters'); + } + return this; +} + +Validator.prototype.isInt = function() { + if (!this.str.match(/^(?:-?(?:0|[1-9][0-9]*))$/)) { + return this.error(this.msg || 'Invalid integer'); + } + return this; +} + +Validator.prototype.isDecimal = function() { + if (!this.str.match(/^(?:-?(?:0|[1-9][0-9]*))?(?:\.[0-9]*)?$/)) { + return this.error(this.msg || 'Invalid decimal'); + } + return this; +} + +Validator.prototype.isFloat = function() { + return this.isDecimal(); +} + +Validator.prototype.notNull = function() { + if (this.str === '') { + return this.error(this.msg || 'Invalid characters'); + } + return this; +} + +Validator.prototype.isNull = function() { + if (this.str !== '') { + return this.error(this.msg || 'Invalid characters'); + } + return this; +} + +Validator.prototype.notEmpty = function() { + if (this.str.match(/^[\s\t\r\n]*$/)) { + return this.error(this.msg || 'String is empty'); + } + return this; +} + +Validator.prototype.equals = function(equals) { + if (this.str != equals) { + return this.error(this.msg || 'Not equal'); + } + return this; +} + +Validator.prototype.contains = function(str) { + if (this.str.indexOf(str) === -1) { + return this.error(this.msg || 'Invalid characters'); + } + return this; +} + +Validator.prototype.notContains = function(str) { + if (this.str.indexOf(str) >= 0) { + return this.error(this.msg || 'Invalid characters'); + } + return this; +} + +Validator.prototype.regex = Validator.prototype.is = function(pattern, modifiers) { + if (typeof pattern !== 'function') { + pattern = new RegExp(pattern, modifiers); + } + if (! this.str.match(pattern)) { + return this.error(this.msg || 'Invalid characters'); + } + return this; +} + +Validator.prototype.notRegex = Validator.prototype.not = function(pattern, modifiers) { + if (typeof pattern !== 'function') { + pattern = new RegExp(pattern, modifiers); + } + if (this.str.match(pattern)) { + return this.error(this.msg || 'Invalid characters'); + } + return this; +} + +Validator.prototype.len = function(min, max) { + if (this.str.length < min) { + return this.error(this.msg || 'String is too small'); + } + if (typeof max !== undefined && this.str.length > max) { + return this.error(this.msg || 'String is too large'); + } + return this; +} + +//Thanks to github.com/sreuter for the idea. +Validator.prototype.isUUID = function(version) { + if (version == 3 || version == 'v3') { + pattern = /[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i; + } else if (version == 4 || version == 'v4') { + pattern = /[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i; + } else { + pattern = /[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(pattern)) { + return this.error(this.msg || 'Not a UUID'); + } + return this; +} + +Validator.prototype.isDate = function() { + var pattern = /^([0-1]{0,1}[0-9]{1})\/([0-3]{0,1}[0-9]{1})\/([0-9]{4})$/; + var result = pattern.exec(this.str); + + if (!result || result.length != 4 ) { + return this.error(this.msg || 'Not a date'); + } + + var dt = new Date(this.str); + + if ( dt.getFullYear() != parseInt(result[3]) + || dt.getMonth() + 1 != parseInt(result[1]) + || dt.getDate() != parseInt(result[2]) + ) { + return this.error(this.msg || 'Not a date'); + } + + return this; +} + +Validator.prototype.in = function(options) { + if (options && typeof options.indexOf === 'function') { + if (!~options.indexOf(this.str)) { + return this.error(this.msg || 'Unexpected value'); + } + return this; + } else { + return this.error(this.msg || 'Invalid in() argument'); + } +} + +Validator.prototype.notIn = function(options) { + if (options && typeof options.indexOf === 'function') { + if (options.indexOf(this.str) !== -1) { + return this.error(this.msg || 'Unexpected value'); + } + return this; + } else { + return this.error(this.msg || 'Invalid notIn() argument'); + } +} + diff --git a/node_modules/validator/lib/xss.js b/node_modules/validator/lib/xss.js new file mode 100755 index 0000000..06b074e --- /dev/null +++ b/node_modules/validator/lib/xss.js @@ -0,0 +1,198 @@ +//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]', + '': '-->', + ' 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: + //Google + str = decodeURIComponent(str); + + //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(/]*?)(>|$)/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|]*?)(\\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|/gi, '[removed]'); + } + + } 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: + //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) { + out = ''; + + str.replace(/\\s*[a-z\-]+\\s*=\\s*(?:\042|\047)(?:[^\\1]*?)\\1/gi, function(m) { + $out += m.replace(/\/\*.*?\*\//g, ''); + }); + + return out; +} diff --git a/node_modules/validator/package.json b/node_modules/validator/package.json new file mode 100644 index 0000000..0458d27 --- /dev/null +++ b/node_modules/validator/package.json @@ -0,0 +1,27 @@ +{ "name" : "validator", + "description" : "Data validation, filtering and sanitization for node.js", + "version" : "0.2.6", + "homepage" : "http://github.com/chriso/node-validator", + "keywords" : ["validator", "validation", "assert", "params", "sanitization", "xss", "entities", "sanitize", "sanitisation", "input"], + "author" : "Chris O'Hara ", + "main" : "./lib", + "directories" : { "lib" : "./lib" }, + "bugs" : + { "mail" : "", + "web" : "http://github.com/chriso/node-validator/issues" + }, + "repository": { + "type": "git", + "url": "http://github.com/chriso/node-validator.git" + }, + "contributors": [ + { "name": "PING", "github": "https://github.com/PlNG" }, + { "name": "Dan VerWeire", "github": "https://github.com/wankdanker" } + ], + "engines" : { "node" : ">=0.2.2" }, + "licenses" : + [ { "type" : "MIT" + , "url" : "http://github.com/chriso/node-validator/raw/master/LICENSE" + } + ] +} diff --git a/node_modules/validator/test.js b/node_modules/validator/test.js new file mode 100644 index 0000000..e290480 --- /dev/null +++ b/node_modules/validator/test.js @@ -0,0 +1,4 @@ + +var xss = require('./validator').xssClean; + +console.log(xss('I <3 this')); diff --git a/node_modules/validator/test/filter.test.js b/node_modules/validator/test/filter.test.js new file mode 100755 index 0000000..4210c7c --- /dev/null +++ b/node_modules/validator/test/filter.test.js @@ -0,0 +1,134 @@ +var node_validator = require('../lib'), + Filter = new node_validator.Filter(), + assert = require('assert'); + +module.exports = { + 'test #ifNull()': function () { + //Make sure sanitize returns the new string + assert.equal(5, Filter.sanitize('').ifNull(5)); + assert.equal('abc', Filter.sanitize().ifNull('abc')); + + //Modify Filter.modify() to automatically replace a var with the sanitized version + var param = ''; + Filter.modify = function(str) { + this.str = str; + param = str; + } + Filter.sanitize(param).ifNull('foobar'); + assert.equal('foobar', param); + }, + + 'test #toBoolean()': function () { + assert.equal(true, Filter.sanitize('1').toBoolean()); + assert.equal(true, Filter.sanitize('true').toBoolean()); + assert.equal(true, Filter.sanitize('foobar').toBoolean()); + assert.equal(true, Filter.sanitize(5).toBoolean()); + assert.equal(true, Filter.sanitize(' ').toBoolean()); + + assert.equal(false, Filter.sanitize('0').toBoolean()); + assert.equal(false, Filter.sanitize('false').toBoolean()); + assert.equal(false, Filter.sanitize('').toBoolean()); + assert.equal(false, Filter.sanitize('false').toBoolean()); + }, + + 'test #toBooleanStrict()': function () { + assert.equal(true, Filter.sanitize('1').toBooleanStrict()); + assert.equal(true, Filter.sanitize('true').toBooleanStrict()); + + assert.equal(false, Filter.sanitize('foobar').toBooleanStrict()); + assert.equal(false, Filter.sanitize(5).toBooleanStrict()); + assert.equal(false, Filter.sanitize(' ').toBooleanStrict()); + assert.equal(false, Filter.sanitize('0').toBooleanStrict()); + assert.equal(false, Filter.sanitize('false').toBooleanStrict()); + assert.equal(false, Filter.sanitize('').toBooleanStrict()); + assert.equal(false, Filter.sanitize('false').toBooleanStrict()); + }, + + 'test #trim()': function () { + //Test trim() with spaces + assert.equal('abc', Filter.sanitize(' abc').trim()); + assert.equal('abc', Filter.sanitize('abc ').trim()); + assert.equal('abc', Filter.sanitize(' abc ').trim()); + + //Test trim() with \t + assert.equal('abc', Filter.sanitize(' abc').trim()); + assert.equal('abc', Filter.sanitize('abc ').trim()); + assert.equal('abc', Filter.sanitize(' abc ').trim()); + + //Test trim() with a mixture of \t, \s, \r and \n + assert.equal('abc', Filter.sanitize(' \r\n abc\r\n ').trim()); + + //Test trim() with custom chars + assert.equal('2', Filter.sanitize('000020000').trim('0')); + assert.equal('202', Filter.sanitize('01000202100101').trim('01')); + }, + + 'test #ltrim()': function () { + //Test ltrim() with spaces + assert.equal('abc', Filter.sanitize(' abc').ltrim()); + assert.equal('abc ', Filter.sanitize(' abc ').ltrim()); + + //Test ltrim() with \t + assert.equal('abc', Filter.sanitize(' abc').ltrim()); + assert.equal('abc ', Filter.sanitize(' abc ').ltrim()); + + //Test ltrim() with a mixture of \t, \s, \r and \n + assert.equal('abc\r\n', Filter.sanitize(' \r\n abc\r\n').ltrim()); + + //Test ltrim() with custom chars + assert.equal('20', Filter.sanitize('000020').ltrim('0')); + assert.equal('201', Filter.sanitize('010100201').ltrim('01')); + }, + + 'test #rtrim()': function () { + //Test rtrim() with spaces + assert.equal(' abc', Filter.sanitize(' abc ').rtrim()); + assert.equal('abc', Filter.sanitize('abc ').rtrim()); + + //Test rtrim() with \t + assert.equal(' abc', Filter.sanitize(' abc').rtrim()); + assert.equal('abc', Filter.sanitize('abc ').rtrim()); + + //Test rtrim() with a mixture of \t, \s, \r and \n + assert.equal(' \r\n abc', Filter.sanitize(' \r\n abc\r\n ').rtrim()); + + //Test rtrim() with custom chars + assert.equal('02', Filter.sanitize('02000').rtrim('0')); + assert.equal('012', Filter.sanitize('01201001').rtrim('01')); + }, + + 'test #toInt()': function () { + assert.ok(3 === Filter.sanitize('3').toInt()); + assert.ok(3 === Filter.sanitize(' 3 ').toInt()); + }, + + 'test #toFloat()': function () { + assert.ok(3 === Filter.sanitize('3.').toFloat()); + assert.ok(3 === Filter.sanitize(' 3 ').toFloat()); + assert.ok(0 === Filter.sanitize('.0').toFloat()); + assert.ok(13.13 === Filter.sanitize('13.13').toFloat()); + }, + + 'test #entityDecode()': function () { + assert.equal('&', Filter.sanitize('&').entityDecode()); + assert.equal('&&', Filter.sanitize('&&').entityDecode()); + assert.equal('""', Filter.sanitize('""').entityDecode()); + assert.equal('€', Filter.sanitize('¤').entityDecode()); + assert.equal("'", Filter.sanitize("'").entityDecode()); + assert.equal("'", Filter.sanitize("'").entityDecode()); + }, + + 'test #entityEncode()': function () { + assert.equal('&', Filter.sanitize('&').entityEncode()); + assert.equal('&&', Filter.sanitize('&&').entityEncode()); + assert.equal(''', Filter.sanitize("'").entityEncode()); + assert.equal('""', Filter.sanitize('""').entityEncode()); + 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()); + } +} diff --git a/node_modules/validator/test/validator.test.js b/node_modules/validator/test/validator.test.js new file mode 100644 index 0000000..a732b43 --- /dev/null +++ b/node_modules/validator/test/validator.test.js @@ -0,0 +1,454 @@ +var node_validator = require('../lib'), + Validator = new node_validator.Validator(), + assert = require('assert'); + +module.exports = { + 'test #isEmail()': function () { + //Try some invalid emails + var invalid = [ + 'invalidemail@', + 'invalid.com', + '@invalid.com' + ]; + invalid.forEach(function(email) { + try { + Validator.check(email, 'Invalid').isEmail(); + assert.ok(false, 'Invalid email ('+email+') passed validation'); + } catch(e) { + assert.equal('Invalid', e.message); + } + }); + + //Now try some valid ones + var valid = [ + 'foo@bar.com', + 'x@x.x', + 'foo@bar.com.au', + 'foo+bar@bar.com' + ]; + try { + valid.forEach(function(email) { + Validator.check(email).isEmail(); + }); + } catch(e) { + assert.ok(false, 'A valid email did not pass validation'); + } + }, + + 'test #isUrl()': function () { + //Try some invalid URLs + var invalid = [ + 'xyz://foobar.com', //Only http, https and ftp are valid + 'invalid/', + 'invalid.x', + 'invalid.', + '.com', + 'http://com/', + 'http://300.0.0.1/' + ]; + invalid.forEach(function(url) { + try { + Validator.check(url, 'Invalid').isUrl(); + assert.ok(false, 'Invalid url ('+url+') passed validation'); + } catch(e) { + assert.equal('Invalid', e.message); + } + }); + + //Now try some valid ones + var valid = [ + 'foobar.com', + 'www.foobar.com', + 'foobar.com/', + 'valid.au', + 'http://www.foobar.com/', + 'https://www.foobar.com/', + 'ftp://www.foobar.com/', + 'http://www.foobar.com/~foobar', + 'http://user:pass@www.foobar.com/', + 'http://127.0.0.1/', + 'http://255.255.255.255/' + ]; + try { + valid.forEach(function(url) { + Validator.check(url).isUrl(); + }); + } catch(e) { + assert.ok(false, 'A valid url did not pass validation'); + } + }, + + 'test #isIP()': function () { + //Try some invalid IPs + var invalid = [ + 'abc', + '256.0.0.0', + '0.0.0.256' + ]; + invalid.forEach(function(ip) { + try { + Validator.check(ip, 'Invalid').isIP(); + assert.ok(false, 'Invalid IP ('+ip+') passed validation'); + } catch(e) { + assert.equal('Invalid', e.message); + } + }); + + //Now try some valid ones + var valid = [ + '127.0.0.1', + '0.0.0.0', + '255.255.255.255', + '1.2.3.4' + ]; + try { + valid.forEach(function(ip) { + Validator.check(ip).isIP(); + }); + } catch(e) { + assert.ok(false, 'A valid IP did not pass validation'); + } + }, + + 'test #isAlpha()': function () { + assert.ok(Validator.check('abc').isAlpha()); + assert.ok(Validator.check('ABC').isAlpha()); + assert.ok(Validator.check('FoObAr').isAlpha()); + + ['123',123,'abc123',' ',''].forEach(function(str) { + try { + Validator.check(str).isAlpha(); + assert.ok(false, 'isAlpha failed'); + } catch (e) {} + }); + }, + + 'test #isAlphanumeric()': function () { + assert.ok(Validator.check('abc13').isAlphanumeric()); + assert.ok(Validator.check('123').isAlphanumeric()); + assert.ok(Validator.check('F1oO3bAr').isAlphanumeric()); + + ['(*&ASD',' ','.',''].forEach(function(str) { + try { + Validator.check(str).isAlphanumeric(); + assert.ok(false, 'isAlphanumeric failed'); + } catch (e) {} + }); + }, + + 'test #isNumeric()': function () { + assert.ok(Validator.check('123').isNumeric()); + assert.ok(Validator.check('00123').isNumeric()); + assert.ok(Validator.check('-00123').isNumeric()); + assert.ok(Validator.check('0').isNumeric()); + assert.ok(Validator.check('-0').isNumeric()); + + ['123.123',' ','.',''].forEach(function(str) { + try { + Validator.check(str).isNumeric(); + assert.ok(false, 'isNumeric failed'); + } catch (e) {} + }); + }, + + 'test #isLowercase()': function () { + assert.ok(Validator.check('abc').isLowercase()); + assert.ok(Validator.check('foobar').isLowercase()); + assert.ok(Validator.check('a').isLowercase()); + assert.ok(Validator.check('123').isLowercase()); + assert.ok(Validator.check('abc123').isLowercase()); + + ['123A','ABC','.',''].forEach(function(str) { + try { + Validator.check(str).isLowercase(); + assert.ok(false, 'isLowercase failed'); + } catch (e) {} + }); + }, + + 'test #isUppercase()': function () { + assert.ok(Validator.check('FOOBAR').isUppercase()); + assert.ok(Validator.check('A').isUppercase()); + assert.ok(Validator.check('123').isUppercase()); + assert.ok(Validator.check('ABC123').isUppercase()); + + ['abc','123aBC','.',''].forEach(function(str) { + try { + Validator.check(str).isUppercase(); + assert.ok(false, 'isUpper failed'); + } catch (e) {} + }); + }, + + 'test #isInt()': function () { + assert.ok(Validator.check('13').isInt()); + assert.ok(Validator.check('123').isInt()); + assert.ok(Validator.check('0').isInt()); + assert.ok(Validator.check(0).isInt()); + assert.ok(Validator.check(123).isInt()); + assert.ok(Validator.check('-0').isInt()); + + ['123.123','01','000',' ',''].forEach(function(str) { + try { + Validator.check(str).isInt(); + assert.ok(false, 'isInt failed'); + } catch (e) {} + }); + }, + + 'test #isDecimal()': function () { + assert.ok(Validator.check('123').isDecimal()); + assert.ok(Validator.check('123.').isDecimal()); + assert.ok(Validator.check('123.123').isDecimal()); + assert.ok(Validator.check('-123.123').isDecimal()); + assert.ok(Validator.check('0.123').isDecimal()); + assert.ok(Validator.check('.123').isDecimal()); + assert.ok(Validator.check('.0').isDecimal()); + assert.ok(Validator.check('0').isDecimal()); + assert.ok(Validator.check('-0').isDecimal()); + + ['-.123','01.123',' ',''].forEach(function(str) { + try { + Validator.check(str).isDecimal(); + assert.ok(false, 'isDecimal failed'); + } catch (e) {} + }); + }, + + //Alias for isDecimal() + 'test #isFloat()': function () { + assert.ok(Validator.check('0.5').isFloat()); + }, + + 'test #isNull()': function () { + assert.ok(Validator.check('').isNull()); + assert.ok(Validator.check().isNull()); + + [' ','123','abc'].forEach(function(str) { + try { + Validator.check(str).isNull(); + assert.ok(false, 'isNull failed'); + } catch (e) {} + }); + }, + + 'test #notNull()': function () { + assert.ok(Validator.check('abc').notNull()); + assert.ok(Validator.check('123').notNull()); + assert.ok(Validator.check(' ').notNull()); + + [false,''].forEach(function(str) { + try { + Validator.check(str).notNull(); + assert.ok(false, 'notNull failed'); + } catch (e) {} + }); + }, + + 'test #notEmpty()': function () { + assert.ok(Validator.check('abc').notEmpty()); + assert.ok(Validator.check('123').notEmpty()); + assert.ok(Validator.check(' 123 ').notEmpty()); + + [false,' ','\r\n',' ','', NaN].forEach(function(str) { + try { + Validator.check(str).notEmpty(); + assert.ok(false, 'notEmpty failed'); + } catch (e) {} + }); + }, + + 'test #equals()': function () { + assert.ok(Validator.check('abc').equals('abc')); + assert.ok(Validator.check('123').equals(123)); + assert.ok(Validator.check(' ').equals(' ')); + assert.ok(Validator.check().equals('')); + + try { + Validator.check(123).equals('abc'); + assert.ok(false, 'equals failed'); + } catch (e) {} + + try { + Validator.check('').equals(' '); + assert.ok(false, 'equals failed'); + } catch (e) {} + }, + + 'test #contains()': function () { + assert.ok(Validator.check('abc').contains('abc')); + assert.ok(Validator.check('foobar').contains('oo')); + assert.ok(Validator.check('abc').contains('a')); + assert.ok(Validator.check(' ').contains(' ')); + assert.ok(Validator.check('abc').contains('')); + + try { + Validator.check(123).contains('abc'); + assert.ok(false, 'contains failed'); + } catch (e) {} + + try { + Validator.check('\t').contains('\t\t'); + assert.ok(false, 'contains failed'); + } catch (e) {} + }, + + 'test #notContains()': function () { + assert.ok(Validator.check('abc').notContains('a ')); + assert.ok(Validator.check('foobar').notContains('foobars')); + assert.ok(Validator.check('abc').notContains('123')); + + try { + Validator.check(123).notContains(1); + assert.ok(false, 'notContains failed'); + } catch (e) {} + + try { + Validator.check(' ').contains(''); + assert.ok(false, 'notContains failed'); + } catch (e) {} + }, + + 'test #regex()': function () { + assert.ok(Validator.check('abc').regex(/a/)); + assert.ok(Validator.check('abc').regex(/^abc$/)); + assert.ok(Validator.check('abc').regex('abc')); + assert.ok(Validator.check('ABC').regex(/^abc$/i)); + assert.ok(Validator.check('ABC').regex('abc', 'i')); + assert.ok(Validator.check(12390947686129).regex(/^[0-9]+$/)); + + //Check the is() alias + assert.ok(Validator.check(12390947686129).is(/^[0-9]+$/)); + + try { + Validator.check(123).regex(/^1234$/); + assert.ok(false, 'regex failed'); + } catch (e) {} + }, + + 'test #notRegex()': function () { + assert.ok(Validator.check('foobar').notRegex(/e/)); + assert.ok(Validator.check('ABC').notRegex('abc')); + assert.ok(Validator.check(12390947686129).notRegex(/a/)); + + //Check the not() alias + assert.ok(Validator.check(12390947686129).not(/^[a-z]+$/)); + + try { + Validator.check(123).notRegex(/123/); + assert.ok(false, 'regex failed'); + } catch (e) {} + }, + + 'test #len()': function () { + assert.ok(Validator.check('a').len(1)); + assert.ok(Validator.check(123).len(2)); + assert.ok(Validator.check(123).len(2, 4)); + assert.ok(Validator.check(12).len(2,2)); + + try { + Validator.check('abc').len(4); + assert.ok(false, 'len failed'); + } catch (e) {} + try { + Validator.check('abcd').len(1, 3); + assert.ok(false, 'len failed'); + } catch (e) {} + }, + + 'test #isUUID()': function () { + ////xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + + assert.ok(Validator.check('A987FBC9-4BED-3078-CF07-9141BA07C9F3').isUUID()); + assert.ok(Validator.check('A987FBC9-4BED-3078-CF07-9141BA07C9F3').isUUID(1)); + assert.ok(Validator.check('A987FBC9-4BED-3078-CF07-9141BA07C9F3').isUUID(2)); + assert.ok(Validator.check('A987FBC9-4BED-3078-CF07-9141BA07C9F3').isUUID(3)); + assert.ok(Validator.check('A987FBC9-4BED-4078-8F07-9141BA07C9F3').isUUID(4)); + + try { + Validator.check('A987FBC9-4BED-3078-CF07-9141BA07C9F3').isUUID(4); + assert.ok(false, 'isUUID failed'); + } catch (e) {} + + try { + Validator.check('A987FBC9-4BED-4078-0F07-9141BA07C9F3').isUUID(4); + assert.ok(false, 'isUUID failed'); + } catch (e) {} + + try { + Validator.check('abc').isUUID(); + assert.ok(false, 'isUUID failed'); + } catch (e) {} + + try { + Validator.check('A987FBC932-4BED-3078-CF07-9141BA07C9').isUUID(); + assert.ok(false, 'isUUID failed'); + } catch (e) {} + + try { + Validator.check('A987FBG9-4BED-3078-CF07-9141BA07C9DE').isUUID(); + assert.ok(false, 'isUUID failed'); + } catch (e) {} + + }, + + 'test #in(options)': function () { + + assert.ok(Validator.check('foo').in('foobar')); + assert.ok(Validator.check('foo').in('I love football')); + + assert.ok(Validator.check('foo').in(['foo', 'bar', 'baz'])); + + assert.throws(function() { + Validator.check('foo').in(['bar', 'baz']); + }, /unexpected/i + ); + + assert.throws(function() { + Validator.check('foo').in('bar, baz'); + }, /unexpected/i + ); + + assert.throws(function() { + Validator.check('foo').in(1234567); + }, /invalid/i + ); + + assert.throws(function() { + Validator.check('foo').in({foo:"foo",bar:"bar"}); + }, /invalid/i + ); + }, + + 'test #notIn(options)': function () { + + assert.ok(Validator.check('foo').notIn('bar')); + assert.ok(Validator.check('foo').notIn('awesome')); + + assert.ok(Validator.check('foo').notIn(['foobar', 'bar', 'baz'])); + + assert.throws(function() { + Validator.check('foo').notIn(['foo', 'bar', 'baz']); + }, /unexpected/i + ); + + assert.throws(function() { + Validator.check('foo').notIn('foobar'); + }, /unexpected/i + ); + + assert.throws(function() { + Validator.check('foo').notIn(1234567); + }, /invalid/i + ); + + assert.throws(function() { + Validator.check('foo').notIn({foo:"foo",bar:"bar"}); + }, /invalid/i + ); + } + + + +} + diff --git a/node_modules/validator/validator-min.js b/node_modules/validator/validator-min.js new file mode 100644 index 0000000..a3d146a --- /dev/null +++ b/node_modules/validator/validator-min.js @@ -0,0 +1,23 @@ +/*! + * Copyright (c) 2010 Chris O'Hara + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * 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(a){function l(a){out="",a.replace(/\\s*[a-z\-]+\\s*=\\s*(?:\042|\047)(?:[^\\1]*?)\\1/gi,function(a){$out+=a.replace(/\/\*.*?\*\//g,"")});return out}function k(a){return a.replace(">",">").replace("<","<").replace("\\","\\\\")}function j(){return"!*$^#(@*#&"}function i(a){for(var b in g)a=a.replace(g[b],"");return a}var b={' ':'\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','"':'\u0022','<':'\u003c','>':'\u003e',''':'\u0027','−':'\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'},c=function(a){if(!~a.indexOf("&"))return a;for(var c in b)a=a.replace(new RegExp(c,"g"),b[c]);a=a.replace(/&#x(0*[0-9a-f]{2,5});?/gi,function(a,b){return String.fromCharCode(parseInt(+b,16))}),a=a.replace(/&#([0-9]{2,4});?/gi,function(a,b){return String.fromCharCode(+b)}),a=a.replace(/&/g,"&");return a},d=function(a){a=a.replace(/&/g,"&"),a=a.replace(/'/g,"'");for(var c in b)a=a.replace(new RegExp(b[c],"g"),c);return a};a.entities={encode:d,decode:c};var e={"document.cookie":"[removed]","document.write":"[removed]",".parentNode":"[removed]",".innerHTML":"[removed]","window.location":"[removed]","-moz-binding":"[removed]","":"-->","|<|$)/gi,function(a,b){}),b=i(b),b=b.replace("\t"," ");var g=b;for(var d in e)b=b.replace(d,e[d]);for(var d in f)b=b.replace(new RegExp(d,"i"),f[d]);for(var d in h){var m=h[d].split("").join("\\s*")+"\\s*";b=b.replace(new RegExp("("+m+")(\\W)","ig"),function(a,b,c){return b.replace(/\s+/g,"")+c})}do{var n=b;b.match(/]*?)(>|$)/gi,function(a,b,c){b=l(b.replace("<","").replace(">",""));return a.replace(b,b.replace(/href=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|]*?)(\\s?\/?>|$)/gi,function(a,b,c){b=l(b.replace("<","").replace(">",""));return a.replace(b,b.replace(/src=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|/gi,"[removed]")}while(n!=b);event_handlers=["[^a-z_-]onw*"],c||event_handlers.push("xmlns"),b=b.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",b=b.replace(new RegExp("<(/*\\s*)("+naughty+")([^><]*)([><]*)","gi"),function(a,b,c,d,e){return"<"+b+c+d+e.replace(">",">").replace("<","<")}),b=b.replace(/(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\\s*)\((.*?)\)/gi,"$1$2($3)");for(var d in e)b=b.replace(d,e[d]);for(var d in f)b=b.replace(new RegExp(d,"i"),f[d]);if(c&&b!==g)throw new Error("Image may contain XSS");return b};var m=a.Validator=function(){};m.prototype.check=function(a,b){this.str=a==null||isNaN(a)&&a.length==undefined?"":a+"",this.msg=b,this._errors=[];return this},m.prototype.validate=m.prototype.check,m.prototype.assert=m.prototype.check,m.prototype.error=function(a){throw new Error(a)},m.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},m.prototype.isUrl=function(){if(!this.str.match(/^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?((?:(?:[-\w\d{1-3}]+\.)+(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|edu|co\.uk|ac\.uk|it|fr|tv|museum|asia|local|travel|[a-z]{2}))|((\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)(\.(\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)){3}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$ |\/.,*:;=]|%[a-f\d]{2})*)?$/))return this.error(this.msg||"Invalid URL");return this},m.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},m.prototype.isAlpha=function(){if(!this.str.match(/^[a-zA-Z]+$/))return this.error(this.msg||"Invalid characters");return this},m.prototype.isAlphanumeric=function(){if(!this.str.match(/^[a-zA-Z0-9]+$/))return this.error(this.msg||"Invalid characters");return this},m.prototype.isNumeric=function(){if(!this.str.match(/^-?[0-9]+$/))return this.error(this.msg||"Invalid number");return this},m.prototype.isLowercase=function(){if(!this.str.match(/^[a-z0-9]+$/))return this.error(this.msg||"Invalid characters");return this},m.prototype.isUppercase=function(){if(!this.str.match(/^[A-Z0-9]+$/))return this.error(this.msg||"Invalid characters");return this},m.prototype.isInt=function(){if(!this.str.match(/^(?:-?(?:0|[1-9][0-9]*))$/))return this.error(this.msg||"Invalid integer");return this},m.prototype.isDecimal=function(){if(!this.str.match(/^(?:-?(?:0|[1-9][0-9]*))?(?:\.[0-9]*)?$/))return this.error(this.msg||"Invalid decimal");return this},m.prototype.isFloat=function(){return this.isDecimal()},m.prototype.notNull=function(){if(this.str==="")return this.error(this.msg||"Invalid characters");return this},m.prototype.isNull=function(){if(this.str!=="")return this.error(this.msg||"Invalid characters");return this},m.prototype.notEmpty=function(){if(this.str.match(/^[\s\t\r\n]*$/))return this.error(this.msg||"String is whitespace");return this},m.prototype.equals=function(a){if(this.str!=a)return this.error(this.msg||"Not equal");return this},m.prototype.contains=function(a){if(this.str.indexOf(a)===-1)return this.error(this.msg||"Invalid characters");return this},m.prototype.notContains=function(a){if(this.str.indexOf(a)>=0)return this.error(this.msg||"Invalid characters");return this},m.prototype.regex=m.prototype.is=function(a,b){typeof a!="function"&&(a=new RegExp(a,b));if(!this.str.match(a))return this.error(this.msg||"Invalid characters");return this},m.prototype.notRegex=m.prototype.not=function(a,b){typeof a!="function"&&(a=new RegExp(a,b)),this.str.match(a)&&this.error(this.msg||"Invalid characters");return this},m.prototype.len=function(a,b){this.str.lengthb)return this.error(this.msg||"String is too large");return this},m.prototype.isUUID=function(a){a==3||a=="v3"?pattern=/[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i:a==4||a=="v4"?pattern=/[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i:pattern=/[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(pattern))return this.error(this.msg||"Not a UUID");return this},m.prototype.isDate=function(){var a=/^([0-1]{0,1}[0-9]{1})\/([0-3]{0,1}[0-9]{1})\/([0-9]{4})$/,b=a.exec(this.str);if(b.length!=4)return this.error(this.msg||"Not a date");var c=new Date(this.str);if(c.getFullYear()!=parseInt(b[3])||c.getMonth()+1!=parseInt(b[1])||c.getDate()!=parseInt(b[2]))return this.error(this.msg||"Not a date");return this};var n=a.Filter=function(){},o="\\r\\n\\t\\s";n.prototype.modify=function(a){this.str=a},n.prototype.convert=n.prototype.sanitize=function(a){this.str=a;return this},n.prototype.xss=function(a){this.modify(xssClean(this.str,a));return this.str},n.prototype.entityDecode=function(){this.modify(c(this.str));return this.str},n.prototype.entityEncode=function(){this.modify(d(this.str));return this.str},n.prototype.ltrim=function(a){a=a||o,this.modify(this.str.replace(new RegExp("^["+a+"]+","g"),""));return this.str},n.prototype.rtrim=function(a){a=a||o,this.modify(this.str.replace(new RegExp("["+a+"]+$","g"),""));return this.str},n.prototype.trim=function(a){a=a||o,this.modify(this.str.replace(new RegExp("^["+a+"]+|["+a+"]+$","g"),""));return this.str},n.prototype.ifNull=function(a){(!this.str||this.str==="")&&this.modify(a);return this.str},n.prototype.toFloat=function(){this.modify(parseFloat(this.str));return this.str},n.prototype.toInt=function(a){a=a||10,this.modify(parseInt(this.str),a);return this.str},n.prototype.toBoolean=function(){!this.str||this.str=="0"||this.str=="false"||this.str==""?this.modify(!1):this.modify(!0);return this.str},n.prototype.toBooleanStrict=function(){this.str=="1"||this.str=="true"?this.modify(!0):this.modify(!1);return this.str},a.sanitize=a.convert=function(b){var c=new a.Filter;return c.sanitize(b)},a.check=a.validate=a.assert=function(b,c){var d=new a.Validator;return d.check(b,c)}})(this) diff --git a/node_modules/validator/validator.js b/node_modules/validator/validator.js new file mode 100644 index 0000000..b6f8421 --- /dev/null +++ b/node_modules/validator/validator.js @@ -0,0 +1,838 @@ +/*! + * Copyright (c) 2010 Chris O'Hara + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * 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(exports) { + + var entities = { + ' ': '\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', + '"': '\u0022', + '<': '\u003c', + '>': '\u003e', + ''': '\u0027', + '−': '\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' + }; + + var decode = function (str) { + if (!~str.indexOf('&')) return str; + + //Decode literal entities + for (var i in entities) { + str = str.replace(new RegExp(i, 'g'), entities[i]); + } + + //Decode hex entities + str = str.replace(/&#x(0*[0-9a-f]{2,5});?/gi, function (m, code) { + return String.fromCharCode(parseInt(+code, 16)); + }); + + //Decode numeric entities + str = str.replace(/&#([0-9]{2,4});?/gi, function (m, code) { + return String.fromCharCode(+code); + }); + + str = str.replace(/&/g, '&'); + + return str; + } + + var encode = function (str) { + str = str.replace(/&/g, '&'); + + //IE doesn't accept ' + str = str.replace(/'/g, '''); + + //Encode literal entities + for (var i in entities) { + str = str.replace(new RegExp(entities[i], 'g'), i); + } + + return str; + } + + exports.entities = { + encode: encode, + decode: decode + } + + //This module is adapted from the CodeIgniter framework + //The license is available at http://codeigniter.com/ + + var never_allowed_str = { + 'document.cookie': '[removed]', + 'document.write': '[removed]', + '.parentNode': '[removed]', + '.innerHTML': '[removed]', + 'window.location': '[removed]', + '-moz-binding': '[removed]', + '': '-->', + ' 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: + //Google + str = decodeURIComponent(str); + + //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(/]*?)(>|$)/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|]*?)(\\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|/gi, '[removed]'); + } + + } 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: + //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) { + out = ''; + + str.replace(/\\s*[a-z\-]+\\s*=\\s*(?:\042|\047)(?:[^\\1]*?)\\1/gi, function(m) { + $out += m.replace(/\/\*.*?\*\//g, ''); + }); + + return out; + } + + var Validator = exports.Validator = function() {} + + Validator.prototype.check = function(str, fail_msg) { + this.str = str == null || (isNaN(str) && str.length == undefined) ? '' : str+''; + this.msg = fail_msg; + this._errors = []; + return this; + } + + //Create some aliases - may help code readability + Validator.prototype.validate = Validator.prototype.check; + Validator.prototype.assert = Validator.prototype.check; + + Validator.prototype.error = function(msg) { + throw new Error(msg); + } + + Validator.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; + } + + Validator.prototype.isUrl = function() { + if (!this.str.match(/^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?((?:(?:[-\w\d{1-3}]+\.)+(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|edu|co\.uk|ac\.uk|it|fr|tv|museum|asia|local|travel|[a-z]{2}))|((\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)(\.(\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)){3}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$ |\/.,*:;=]|%[a-f\d]{2})*)?$/)) { + return this.error(this.msg || 'Invalid URL'); + } + return this; + } + + Validator.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; + } + + Validator.prototype.isAlpha = function() { + if (!this.str.match(/^[a-zA-Z]+$/)) { + return this.error(this.msg || 'Invalid characters'); + } + return this; + } + + Validator.prototype.isAlphanumeric = function() { + if (!this.str.match(/^[a-zA-Z0-9]+$/)) { + return this.error(this.msg || 'Invalid characters'); + } + return this; + } + + Validator.prototype.isNumeric = function() { + if (!this.str.match(/^-?[0-9]+$/)) { + return this.error(this.msg || 'Invalid number'); + } + return this; + } + + Validator.prototype.isLowercase = function() { + if (!this.str.match(/^[a-z0-9]+$/)) { + return this.error(this.msg || 'Invalid characters'); + } + return this; + } + + Validator.prototype.isUppercase = function() { + if (!this.str.match(/^[A-Z0-9]+$/)) { + return this.error(this.msg || 'Invalid characters'); + } + return this; + } + + Validator.prototype.isInt = function() { + if (!this.str.match(/^(?:-?(?:0|[1-9][0-9]*))$/)) { + return this.error(this.msg || 'Invalid integer'); + } + return this; + } + + Validator.prototype.isDecimal = function() { + if (!this.str.match(/^(?:-?(?:0|[1-9][0-9]*))?(?:\.[0-9]*)?$/)) { + return this.error(this.msg || 'Invalid decimal'); + } + return this; + } + + Validator.prototype.isFloat = function() { + return this.isDecimal(); + } + + Validator.prototype.notNull = function() { + if (this.str === '') { + return this.error(this.msg || 'Invalid characters'); + } + return this; + } + + Validator.prototype.isNull = function() { + if (this.str !== '') { + return this.error(this.msg || 'Invalid characters'); + } + return this; + } + + Validator.prototype.notEmpty = function() { + if (this.str.match(/^[\s\t\r\n]*$/)) { + return this.error(this.msg || 'String is whitespace'); + } + return this; + } + + Validator.prototype.equals = function(equals) { + if (this.str != equals) { + return this.error(this.msg || 'Not equal'); + } + return this; + } + + Validator.prototype.contains = function(str) { + if (this.str.indexOf(str) === -1) { + return this.error(this.msg || 'Invalid characters'); + } + return this; + } + + Validator.prototype.notContains = function(str) { + if (this.str.indexOf(str) >= 0) { + return this.error(this.msg || 'Invalid characters'); + } + return this; + } + + Validator.prototype.regex = Validator.prototype.is = function(pattern, modifiers) { + if (typeof pattern !== 'function') { + pattern = new RegExp(pattern, modifiers); + } + if (! this.str.match(pattern)) { + return this.error(this.msg || 'Invalid characters'); + } + return this; + } + + Validator.prototype.notRegex = Validator.prototype.not = function(pattern, modifiers) { + if (typeof pattern !== 'function') { + pattern = new RegExp(pattern, modifiers); + } + if (this.str.match(pattern)) { + this.error(this.msg || 'Invalid characters'); + } + return this; + } + + Validator.prototype.len = function(min, max) { + if (this.str.length < min) { + this.error(this.msg || 'String is too small'); + } + if (typeof max !== undefined && this.str.length > max) { + return this.error(this.msg || 'String is too large'); + } + return this; + } + + //Thanks to github.com/sreuter for the idea. + Validator.prototype.isUUID = function(version) { + if (version == 3 || version == 'v3') { + pattern = /[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i; + } else if (version == 4 || version == 'v4') { + pattern = /[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i; + } else { + pattern = /[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(pattern)) { + return this.error(this.msg || 'Not a UUID'); + } + return this; + } + + Validator.prototype.isDate = function() { + var pattern = /^([0-1]{0,1}[0-9]{1})\/([0-3]{0,1}[0-9]{1})\/([0-9]{4})$/; + var result = pattern.exec(this.str); + + if (result.length != 4 ) { + return this.error(this.msg || 'Not a date'); + } + + var dt = new Date(this.str); + + if ( dt.getFullYear() != parseInt(result[3]) + || dt.getMonth() + 1 != parseInt(result[1]) + || dt.getDate() != parseInt(result[2]) + ) { + return this.error(this.msg || 'Not a date'); + } + + return this; + } + + Validator.prototype.in = function(options) { + if (options && typeof options.indexOf === 'function') { + if (!~options.indexOf(this.str)) { + return this.error(this.msg || 'Unexpected value'); + } + return this; + } else { + return this.error(this.msg || 'Invalid in() argument'); + } + } + + Validator.prototype.notIn = function(options) { + if (options && typeof options.indexOf === 'function') { + if (options.indexOf(this.str) !== -1) { + return this.error(this.msg || 'Unexpected value'); + } + return this; + } else { + return this.error(this.msg || 'Invalid notIn() argument'); + } + } + + var Filter = exports.Filter = function() {} + + var whitespace = '\\r\\n\\t\\s'; + + Filter.prototype.modify = function(str) { + this.str = str; + } + + //Create some aliases - may help code readability + Filter.prototype.convert = Filter.prototype.sanitize = function(str) { + this.str = str; + return this; + } + + Filter.prototype.xss = function(is_image) { + this.modify(xssClean(this.str, is_image)); + return this.str; + } + + Filter.prototype.entityDecode = function() { + this.modify(decode(this.str)); + return this.str; + } + + Filter.prototype.entityEncode = function() { + this.modify(encode(this.str)); + return this.str; + } + + Filter.prototype.ltrim = function(chars) { + chars = chars || whitespace; + this.modify(this.str.replace(new RegExp('^['+chars+']+', 'g'), '')); + return this.str; + } + + Filter.prototype.rtrim = function(chars) { + chars = chars || whitespace; + this.modify(this.str.replace(new RegExp('['+chars+']+$', 'g'), '')); + return this.str; + } + + Filter.prototype.trim = function(chars) { + chars = chars || whitespace; + this.modify(this.str.replace(new RegExp('^['+chars+']+|['+chars+']+$', 'g'), '')); + return this.str; + } + + Filter.prototype.ifNull = function(replace) { + if (!this.str || this.str === '') { + this.modify(replace); + } + return this.str; + } + + Filter.prototype.toFloat = function() { + this.modify(parseFloat(this.str)); + return this.str; + } + + Filter.prototype.toInt = function(radix) { + radix = radix || 10; + this.modify(parseInt(this.str), radix); + return this.str; + } + + //Any strings with length > 0 (except for '0' and 'false') are considered true, + //all other strings are false + Filter.prototype.toBoolean = function() { + if (!this.str || this.str == '0' || this.str == 'false' || this.str == '') { + this.modify(false); + } else { + this.modify(true); + } + return this.str; + } + + //String must be equal to '1' or 'true' to be considered true, all other strings + //are false + Filter.prototype.toBooleanStrict = function() { + if (this.str == '1' || this.str == 'true') { + this.modify(true); + } else { + this.modify(false); + } + return this.str; + } + + //Quick access methods + exports.sanitize = exports.convert = function(str) { + var filter = new exports.Filter(); + return filter.sanitize(str); + } + + exports.check = exports.validate = exports.assert = function(str, fail_msg) { + var validator = new exports.Validator(); + return validator.check(str, fail_msg); + } + +})(this); diff --git a/package.json b/package.json new file mode 100644 index 0000000..246cd4f --- /dev/null +++ b/package.json @@ -0,0 +1,45 @@ +{ + "name":"fusker", + "description":"Utilities to mess with hackers", + "version":"0.0.2", + "homepage":"http://github.com/wearefractal/fusker", + "keywords":[ + "fusker", + "hack", + "protection", + "lulz", + "hacker" + ], + "author":"Contra ", + "main":"./lib/fusker.js", + "directories":{ + "lib":"./lib" + }, + "bugs":{ + "mail":"issues@wearefractal.com", + "web":"http://github.com/wearefractal/fusker/issues" + }, + "repository":{ + "type":"git", + "url":"http://github.com/wearefractal/fusker.git" + }, + "contributors":[ + { + "name":"amurray", + "github":"https://github.com/amurray" + }, + { + "name":"tprime", + "github":"https://github.com/tprime" + } + ], + "engines":{ + "node":">=0.2.2" + }, + "licenses":[ + { + "type":"MIT", + "url":"http://github.com/wearefractal/fusker/raw/master/LICENSE" + } + ] +} diff --git a/test.js b/test.js new file mode 100644 index 0000000..d4b9e99 --- /dev/null +++ b/test.js @@ -0,0 +1,17 @@ +//Including this creates global.fusker so no need to assign anything here +require('./lib/fusker'); + +//General configuration +fusker.config.dir = process.cwd(); //Sets the web directory to lock to. Default: process.cwd (Working directory) +fusker.config.level = fusker.levels.HIGH; //None, Low, Fair, High and Extreme - How badly to fuck with the hacker. Default: Fair +fusker.config.banlength = 20; //Length (in minutes) to blacklist attacker IP. Default: 1440 (24 hours) + + +//Configure detections, loaded from mods folder. +//Tests will be executed in order, always put 404 last if you choose to use it +fusker.detect.push('xss'); //Checks URL for common XSS +fusker.detect.push('csrf'); //Checks headers for CSRF attacks +fusker.detect.push('lfi'); //Checks URL for common LFI +fusker.detect.push('404'); //Only use for single page applications and if you want to punish snoopers + +var server = fusker.createServer(8080);