Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: dgunzi/newcookbook
base: 8443d705e1
...
head fork: dgunzi/newcookbook
compare: b2a563d332
  • 5 commits
  • 44 files changed
  • 0 commit comments
  • 1 contributor
Showing with 3,851 additions and 133 deletions.
  1. +4 −2 README.md
  2. +9 −12 app.js
  3. +2 −1  config/config.js
  4. +122 −5 controllers/cookbook.js
  5. +2 −3 controllers/site.js
  6. +106 −0 controllers/upload.js
  7. +110 −0 controllers/user.js
  8. +2 −0  models/index.js
  9. +22 −0 models/user.js
  10. +1 −0  node_modules/validator/.npmignore
  11. +20 −0 node_modules/validator/LICENSE
  12. +220 −0 node_modules/validator/README.md
  13. +15 −0 node_modules/validator/index.html
  14. +1 −0  node_modules/validator/index.js
  15. +291 −0 node_modules/validator/lib/entities.js
  16. +102 −0 node_modules/validator/lib/filter.js
  17. +15 −0 node_modules/validator/lib/index.js
  18. +314 −0 node_modules/validator/lib/validator.js
  19. +203 −0 node_modules/validator/lib/xss.js
  20. +65 −0 node_modules/validator/package.json
  21. +4 −0 node_modules/validator/test.js
  22. +145 −0 node_modules/validator/test/filter.test.js
  23. +8 −0 node_modules/validator/test/run.js
  24. +604 −0 node_modules/validator/test/validator.test.js
  25. +22 −0 node_modules/validator/validator-min.js
  26. +861 −0 node_modules/validator/validator.js
  27. +1 −1  public/css/bootstrap.css
  28. +131 −2 public/css/custom.css
  29. BIN  public/uploads/578a9f40-114e-11e2-948c-b3df81d65ffd.jpg
  30. BIN  public/uploads/71f84200-114f-11e2-948c-b3df81d65ffd.jpg
  31. BIN  public/uploads/834ff480-114f-11e2-948c-b3df81d65ffd.jpg
  32. BIN  public/uploads/8f88ec20-114f-11e2-948c-b3df81d65ffd.jpg
  33. BIN  public/uploads/Thumbs.db
  34. +32 −1 routes.js
  35. BIN  tmp/62d78420-199c-11e2-ac41-797333fe0698
  36. 0  tmp/file
  37. +25 −8 views/article.html
  38. +0 −1  views/common/header.html
  39. +12 −38 views/cookbookview.html
  40. +4 −3 views/{write.html → cookbookwrite.html}
  41. +50 −56 views/index.html
  42. +62 −0 views/user_add.html
  43. +177 −0 views/user_view.html
  44. +87 −0 views/user_views.html
View
6 README.md
@@ -1,2 +1,4 @@
-测试git
-上传
+cookbook
+一个基于 NodeJs + MongoDB 构建菜谱网站
+完成基础框架构建
+下一步将增加用户注册与管理功能
View
21 app.js
@@ -10,16 +10,20 @@ var express = require('express'),
path = require('path');
app.configure(function(){
+
app.set('views', __dirname + '/views');
+
app.use(express.bodyParser());
app.use(express.methodOverride());
- //app.use(express.static(__dirname + '/public'));
+
+ app.use(express.cookieParser());
+ app.use(express.session({
+ secret: config.session_secret
+ }));
+
app.set('view engine', 'html');
app.engine('html', ejs.renderFile);
-
- /*app.use(express.session({
- secret: config.session_secret
- }));*/
+
});
var staticDir = path.join(__dirname, 'public');
@@ -35,12 +39,6 @@ app.configure('production', function(){
app.use(express.errorHandler());
app.use('view cache',true);
- app.use(express.csrf());
-
- app.use(function(req, res, next) {
- res.locals.csrf = req.session ? req.session._csrf : '';
- next();
- });
});
//定义locals变量
@@ -49,7 +47,6 @@ app.locals({
site : site
});
-
// Routes
routes(app);
View
3  config/config.js
@@ -11,7 +11,8 @@ exports.config = {
username : "admin",
password : "admin",
node_port : process.argv[2] || 3000,
- upload_dir : __dirname + '/public/uploads/',
+ upload_dir : '/public/uploads/',
+ tmp_dir : 'tmp',
db : 'mongodb://127.0.0.1:27017/nodeblog'
}
View
127 controllers/cookbook.js
@@ -4,7 +4,61 @@
var models = require('../models');
var cookbook = models.cookbook;
var EventProxy = require('eventproxy').EventProxy;
+var crypto = require('crypto');
+exports.cookbook_write = function(req, res, next){
+ res.render('cookbookwrite', {edit: '0', post: ""});
+}
+
+exports.cookbook_admin = function(req, res){
+
+ var render = function(cookbooks){
+ res.render('article', {cookbooks: cookbooks,edit: '0'});
+ return;
+ }
+
+ var proxy = new EventProxy();
+ proxy.assign('cookbooks', render);
+
+ cookbook.find({}).sort({'datetime' : 1}).execFind(function(err, cookbooks){
+ if(err){
+ res.render('error', {error: '查询记录时出错'});
+ return;
+ }else{
+ proxy.trigger('cookbooks', cookbooks);
+ }
+ });
+
+};
+
+exports.cookbook_edit = function(req, res){
+ if ( req.params.type == 'a' ){
+
+ var render = function(cookbook){
+ res.render('cookbookwrite', {cookbook: cookbook,edit: '1'});
+ return;
+ }
+
+ var proxy = new EventProxy();
+ proxy.assign('cookbook', render);
+
+ cookbook.findOne({id: req.params.id}, function(err, cookbook){
+ if(err){
+ res.render('error', {error: '查询记录时出错'});
+ return;
+ }else{
+ if(!cookbook){
+ res.render('error', {error: '无此信息或已被删除'});
+ return;
+ }else{
+ proxy.trigger('cookbook', cookbook);
+ }
+ }
+ });
+
+ }
+
+};
exports.cookbook_view = function(req, res, next) {
@@ -26,15 +80,79 @@ exports.cookbook_view = function(req, res, next) {
});
}
+
+exports.cookbook_add = function(req, res){
+ var d = new Date();
+ var newCookbook = new cookbook();
+
+ newCookbook.title = req.body.title;
+ newCookbook.url = req.body.url;
+ newCookbook.level = req.body.level;
+ newCookbook.major = req.body.major;
+ newCookbook.minor = req.body.minor;
+ newCookbook.seasoning = req.body.seasoning;
+ newCookbook.steps = req.body.steps;
+ newCookbook.user = 'admin';
+ newCookbook.datetime = d;
+ newCookbook.id = crypto.createHash('md5').update(newCookbook.url + newCookbook.datetime).digest("hex");
+
+ newCookbook.save(function(err){
+ if (err)
+ res.render('error', {error: err});
+ else
+ res.redirect('/cookbook');
+ });
+}
+
+exports.cookbook_update = function(req, res){
+ if (req.params.id){
+ cookbook.findOne({id: req.params.id}, function(err, doc){
+ if (!doc)
+ res.render('error', {error: '无此信息或已被删除'});
+ else {
+ var d = new Date();
+ doc.title = req.body.title;
+ doc.url = req.body.url;
+ doc.level = req.body.level;
+ doc.major = req.body.major;
+ doc.minor = req.body.minor;
+ doc.seasoning = req.body.seasoning;
+ doc.steps = req.body.steps;
+ doc.user = 'admin';
+ doc.datetime = d;
+ doc.save(function(err){
+ if (err)
+ res.render('error', {error: err});
+ else
+ res.redirect('/cookbook');
+ });
+ }
+ });
+ }
+}
+
+exports.cookbook_del = function(req, res){
+ if ( req.params.type == 'a')
+ {
+ if ( req.params.id != "" ){
+ cookbook.remove({id: req.params.id}, function(err){
+ if (err)
+ res.render('error', {error: err});
+ else
+ res.redirect('/cookbook');
+ });
+ }
+ }
+};
+
//指明查询条件查询多条记录
function get_cookbook_by_query(where, opt, cb){
- //console.log(opt)
- cookbook.find(where,function(err, docs){
+ var query = cookbook.find(where).limit(opt.limit).skip(opt.skip).sort({ datetime: 'desc'});;
+ query.exec(function(err, docs){
if(err) return cb(err, null);
- //console.log('docs--'+docs);
if(docs.length == 0) return cb(err, []);
return cb(err, docs);
- }).skip(opt.skip).limit(opt.limit);
+ });
}
//记录总数
@@ -46,6 +164,5 @@ function get_cookbook_counts(where, cb){
}
-
exports.get_cookbook_by_query = get_cookbook_by_query;
exports.get_cookbook_counts = get_cookbook_counts;
View
5 controllers/site.js
@@ -9,7 +9,6 @@ var url = require('url');
exports.index = function (req, res, next) {
var current_page = parseInt(req.query.page, 10) || 1;
- console.log('current_page---'+current_page);
var pathname = url.parse(req.url).pathname;
var render = function (cookbooks, pages){
res.render('index', {
@@ -19,15 +18,15 @@ exports.index = function (req, res, next) {
base_url: pathname
});
}
+
var proxy = new EventProxy();
proxy.assign('cookbooks', 'pages', render);
var where = {};
var limit = config.limit;
- var opt = { skip: (current_page - 1) * limit, limit: limit, sort: [ ['datetime', 'desc'] ]};
+ var opt = {skip: (current_page - 1) * limit, limit: limit,sort: [['datetime', 'desc']]};
cookbookCtrl.get_cookbook_by_query(where, opt, function(err, cookbooks){
if(err) return next(err);
-
proxy.trigger('cookbooks', cookbooks);
});
View
106 controllers/upload.js
@@ -0,0 +1,106 @@
+/**
+ * upload controller
+ */
+// Mainfunction to recieve and process the file upload data asynchronously
+var models = require('../models');
+var imageModel = models.image;
+var uuid = require('node-uuid');
+var fs = require('fs');
+var image = require('../models/image');
+var imageModel = image.imgModel;
+
+ // Mainfunction to recieve and process the file upload data asynchronously
+ exports.uploadFile = function(req, targetdir,tmpdri,callback) {
+
+ // Moves the uploaded file from temp directory to it's destination
+ // and calls the callback with the JSON-data that could be returned.
+ var moveToDestination = function(sourcefile, targetfile, sname) {
+ moveFile(sourcefile, targetfile, function(err) {
+ if(!err){
+ saveFile(req,sname,function(err){
+ if(!err){
+ callback({success: true,filename: sname});
+ }else{
+ callback({success: false, error: err});
+ }
+ });
+ callback({success: true,filename: sname});
+ }else{
+ callback({success: false, error: err});
+ }
+ });
+ };
+
+ // Direct async xhr stream data upload, yeah baby.
+ if(req.xhr) {
+ var fname = req.header('x-file-name');
+ var ex = fname.substring(fname.indexOf('.'));
+ // Be sure you can write to '/tmp/'
+ var id = uuid.v1();
+ var tmpfile = tmpdri + id;
+ var saveName = id+ex;
+
+ // Open a temporary writestream
+ var ws = fs.createWriteStream(tmpfile);
+ ws.on('error', function(err) {
+ console.log("uploadFile() - req.xhr - could not open writestream.");
+ callback({success: false, error: "Sorry, could not open writestream."});
+ });
+ ws.on('close', function(err) {
+ moveToDestination(tmpfile, targetdir+saveName,saveName);
+ });
+
+ // Writing filedata into writestream
+ req.on('data', function(data) {
+ ws.write(data);
+ });
+
+ req.on('end', function() {
+ ws.end();
+ });
+ }
+ // Old form-based upload
+ else {
+ moveToDestination(req.files.qqfile.path, targetdir+req.files.qqfile.name,req.files.qqfile.name);
+ }
+ };
+
+ var saveFile = function(req,sname,callback){
+ var image = new imageModel();
+ var oname = req.header('x-file-name');
+ var user = 'admin';
+
+ image.sname = sname;
+ image.oname = oname;
+ image.user = user;
+ image.flag = '0';
+
+ image.save(function(err){
+ if (err)
+ callback('Sorry, can not save file to datebase');
+ else
+ callback();
+ });
+ };
+
+ // Moves a file asynchronously over partition borders
+ var moveFile = function(source, dest, callback) {
+ var is = fs.createReadStream(source)
+
+ is.on('error', function(err) {
+ console.log('moveFile() - Could not open readstream.');
+ callback('Sorry, could not open readstream.')
+ });
+ is.on('end', function() {
+ fs.unlinkSync(source);
+ callback();
+ });
+
+ var os = fs.createWriteStream(dest);
+ os.on('error', function(err) {
+ console.log('moveFile() - Could not open writestream.');
+ callback('Sorry, could not open writestream.');
+ });
+
+ is.pipe(os);
+ };
View
110 controllers/user.js
@@ -0,0 +1,110 @@
+/*
+ * user.js 用户编辑和添加
+ *
+ * */
+
+var config = require('../config/config').config;
+var crypto = require('crypto');
+var models = require('../models');
+var User = models.User;
+var check = require('validator').check,
+ sanitize = require('validator').sanitize;
+
+
+//设置缓存函数
+function gen_session(user, res, req){
+ var auth_token = encrypt(user._id + '\t' + user.user_name + '\t' + user.password + '\t' + user.email, config.session_secret);
+ res.cookie(config.auth_cookie_name, auth_token, {path: '/',maxAge: 1000 * 60 * 60}); //cookie 有效期1个小时
+ req.session.user = user;
+ req.session.cookie.maxAge = 1000 * 60 * 60;
+}
+
+//对称加密函数
+function encrypt(str, secret){
+ var cipher = crypto.createCipher('aes192', secret);
+ var enc = cipher.update(str, 'utf8', 'hex');
+ enc += cipher.final('hex');
+ return enc;
+}
+
+function decrypt(str, secret){
+ var decipher = crypto.createDecipher('aes192', secret);
+ var dec = decipher.update(str, 'hex', 'utf8');
+ dec += decipher.final('utf8');
+ return dec;
+}
+
+function md5(str) {
+ var md5sum = crypto.createHash('md5');
+ md5sum.update(str);
+ str = md5sum.digest('hex');
+ return str;
+}
+
+//add view
+exports.add_html = function(req, res, next){
+ res.render('user_add');
+};
+
+//add user action
+exports.add_action = function(req, res, next){
+ var user_name = sanitize(req.body.user_name).trim();
+ user_name = sanitize(user_name).xss();
+ var password = sanitize(req.body.password).trim();
+ password = sanitize(password).xss();
+ var email = sanitize(req.body.email).trim();
+ email = sanitize(email).xss();
+ if(user_name == '' || email == '' || password == ''){
+ res.render('user_add', {error: '信息不能为空', user_name: user_name, email: email});
+ return;
+ }
+ //验证用户名
+ try{
+ check(user_name, '用户名只能使用字母和数字').isAlphanumeric();
+ }catch(e){
+ res.render('user_add',{error: e.message, user_name: user_name, email: email});
+ return;
+ }
+ //验证电子邮箱
+ try{
+ check(email, '不正确的电子邮箱').isEmail();
+ }catch(e){
+ res.render('user_add',{error: e.message, user_name: user_name, email: email});
+ return;
+ }
+
+ User.find({'$or':[{'user_name': user_name},{'email': email}]}, function(err, userRow){
+ if(err){
+ return next(err);
+ }
+ if(userRow.length > 0){
+ res.render('user_add', {error: '用户名或邮箱已被使用,请重新输入',user_name: user_name,email: email});
+ return;
+ }
+
+ password = md5(password);
+ user = new User();
+ user.user_name = user_name;
+ user.password = password;
+ user.email = email;
+ user.create_time = Date.now();
+
+ user.save(function(err){
+ if(err) return next(err);
+
+ User.findOne({'user_name': user_name}, function(err, userRow){
+ if(err) return next(err);
+
+ if(userRow){
+ gen_session(userRow, res, req);
+
+ res.redirect('/');
+ }else{
+ res.render('login',{error: '没有此用户,或已被删除'});
+ return;
+ }
+ });
+ });
+ });
+
+}
View
2  models/index.js
@@ -13,5 +13,7 @@ mongoose.connect(config.db, function(err){
//models
require('./cookbook');
+require('./user');
exports.cookbook = mongoose.model('cookbook');
+exports.User = mongoose.model('User');
View
22 models/user.js
@@ -0,0 +1,22 @@
+/*
+ * user.js 用户管理数据库
+ * author: moskito
+ * create time: 2012-07-14
+ * */
+
+var mongoose = require('mongoose');
+var Schema = mongoose.Schema;
+var ObjectId = Schema.ObjectId;
+
+var userSchema = new Schema({
+ user_name : {type : String},
+ password : {type : String},
+ create_time : {type : Date, default : Date.now},
+ update_at : {type: Date, default : Date.now},
+ email : {type : String},
+ edit_id: {type : ObjectId},
+ reply_count: {type : Number, default : 0},
+ article_count: {type : Number, default : 0}
+});
+
+mongoose.model('User',userSchema);
View
1  node_modules/validator/.npmignore
@@ -0,0 +1 @@
+.DS_Store
View
20 node_modules/validator/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2010 Chris O'Hara <cohara87@gmail.com>
+
+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.
View
220 node_modules/validator/README.md
@@ -0,0 +1,220 @@
+**node-validator is a library of string validation, filtering and sanitization methods.**
+
+To install node-validator, use [npm](http://github.com/isaacs/npm):
+
+```bash
+$ npm install validator
+```
+
+To use the library in the browser, include `validator-min.js`
+
+## Example
+
+```javascript
+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('&lt;a&gt;').entityDecode(); //'<a>'
+```
+
+## 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.
+
+If you are using the [express.js framework](https://github.com/visionmedia/express) you can use the [express-validator middleware](https://github.com/ctavan/express-validator) to seamlessly integrate node-validator.
+
+Example `http://localhost:8080/?zip=12345&foo=1&textarea=large_string`
+
+```javascript
+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
+
+```javascript
+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
+isUUID(version) //Version can be 3 or 4 or empty, see http://en.wikipedia.org/wiki/Universally_unique_identifier
+isDate() //Uses Date.parse() - regex is probably a better choice
+isAfter(date) //Argument is optional and defaults to today
+isBefore(date) //Argument is optional and defaults to today
+isIn(options) //Accepts an array or string
+notIn(options)
+max(val)
+min(val)
+isArray()
+isCreditCard() //Will work against Visa, MasterCard, American Express, Discover, Diners Club, and JCB card numbering formats
+```
+
+## List of sanitization / filter methods
+
+```javascript
+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
+
+```javascript
+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
+
+```javascript
+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
+
+```javascript
+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()`
+
+```javascript
+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
+
+```javascript
+var Validator = require('validator').Validator;
+var v = new Validator();
+v.error = function(msg) {
+ console.log('Fail');
+}
+v.check('abc').isInt(); //'Fail'
+```
+
+You might want to collect errors instead of throwing each time
+
+```javascript
+Validator.prototype.error = function (msg) {
+ this._errors.push(msg);
+}
+
+Validator.prototype.getErrors = function () {
+ return this._errors;
+}
+
+var validator = new Validator();
+
+validator.check('abc').isEmail();
+validator.check('hello').len(10,30);
+
+var errors = validator.getErrors(); // ['Invalid email', 'String is too small']
+```
+
+## Contributors
+
+- [PING](https://github.com/PlNG) - Fixed entity encoding
+- [Dan VerWeire](https://github.com/wankdanker) - Modified the behaviour of the error handler
+- [ctavan](https://github.com/ctavan) - Added isArray and isUUID()
+- [foxbunny](https://github.com/foxbunny) - Added min(), max(), isAfter(), isBefore(), and improved isDate()
+- [oris](https://github.com/orls) - Addded in()
+
+## LICENSE
+
+(MIT License)
+
+Copyright (c) 2010 Chris O'Hara <cohara87@gmail.com>
+
+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.
View
15 node_modules/validator/index.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<script type="text/javascript" src="validator-min.js"></script>
+</head>
+<body>
+<script type="text/javascript">
+try {
+ check('ad>dfds@fsdfsd.com').isEmail();
+} catch (e) {
+ console.log('ok');
+}
+console.log(sanitize('aaa<a>bbb</a>').xss());
+</script>
+</body>
+</html>
View
1  node_modules/validator/index.js
@@ -0,0 +1 @@
+exports = module.exports = require('./lib');
View
291 node_modules/validator/lib/entities.js
@@ -0,0 +1,291 @@
+var entities = {
+ '&nbsp;': '\u00a0',
+ '&iexcl;': '\u00a1',
+ '&cent;': '\u00a2',
+ '&pound;': '\u00a3',
+ '&curren;': '\u20ac',
+ '&yen;': '\u00a5',
+ '&brvbar;': '\u0160',
+ '&sect;': '\u00a7',
+ '&uml;': '\u0161',
+ '&copy;': '\u00a9',
+ '&ordf;': '\u00aa',
+ '&laquo;': '\u00ab',
+ '&not;': '\u00ac',
+ '&shy;': '\u00ad',
+ '&reg;': '\u00ae',
+ '&macr;': '\u00af',
+ '&deg;': '\u00b0',
+ '&plusmn;': '\u00b1',
+ '&sup2;': '\u00b2',
+ '&sup3;': '\u00b3',
+ '&acute;': '\u017d',
+ '&micro;': '\u00b5',
+ '&para;': '\u00b6',
+ '&middot;': '\u00b7',
+ '&cedil;': '\u017e',
+ '&sup1;': '\u00b9',
+ '&ordm;': '\u00ba',
+ '&raquo;': '\u00bb',
+ '&frac14;': '\u0152',
+ '&frac12;': '\u0153',
+ '&frac34;': '\u0178',
+ '&iquest;': '\u00bf',
+ '&Agrave;': '\u00c0',
+ '&Aacute;': '\u00c1',
+ '&Acirc;': '\u00c2',
+ '&Atilde;': '\u00c3',
+ '&Auml;': '\u00c4',
+ '&Aring;': '\u00c5',
+ '&AElig;': '\u00c6',
+ '&Ccedil;': '\u00c7',
+ '&Egrave;': '\u00c8',
+ '&Eacute;': '\u00c9',
+ '&Ecirc;': '\u00ca',
+ '&Euml;': '\u00cb',
+ '&Igrave;': '\u00cc',
+ '&Iacute;': '\u00cd',
+ '&Icirc;': '\u00ce',
+ '&Iuml;': '\u00cf',
+ '&ETH;': '\u00d0',
+ '&Ntilde;': '\u00d1',
+ '&Ograve;': '\u00d2',
+ '&Oacute;': '\u00d3',
+ '&Ocirc;': '\u00d4',
+ '&Otilde;': '\u00d5',
+ '&Ouml;': '\u00d6',
+ '&times;': '\u00d7',
+ '&Oslash;': '\u00d8',
+ '&Ugrave;': '\u00d9',
+ '&Uacute;': '\u00da',
+ '&Ucirc;': '\u00db',
+ '&Uuml;': '\u00dc',
+ '&Yacute;': '\u00dd',
+ '&THORN;': '\u00de',
+ '&szlig;': '\u00df',
+ '&agrave;': '\u00e0',
+ '&aacute;': '\u00e1',
+ '&acirc;': '\u00e2',
+ '&atilde;': '\u00e3',
+ '&auml;': '\u00e4',
+ '&aring;': '\u00e5',
+ '&aelig;': '\u00e6',
+ '&ccedil;': '\u00e7',
+ '&egrave;': '\u00e8',
+ '&eacute;': '\u00e9',
+ '&ecirc;': '\u00ea',
+ '&euml;': '\u00eb',
+ '&igrave;': '\u00ec',
+ '&iacute;': '\u00ed',
+ '&icirc;': '\u00ee',
+ '&iuml;': '\u00ef',
+ '&eth;': '\u00f0',
+ '&ntilde;': '\u00f1',
+ '&ograve;': '\u00f2',
+ '&oacute;': '\u00f3',
+ '&ocirc;': '\u00f4',
+ '&otilde;': '\u00f5',
+ '&ouml;': '\u00f6',
+ '&divide;': '\u00f7',
+ '&oslash;': '\u00f8',
+ '&ugrave;': '\u00f9',
+ '&uacute;': '\u00fa',
+ '&ucirc;': '\u00fb',
+ '&uuml;': '\u00fc',
+ '&yacute;': '\u00fd',
+ '&thorn;': '\u00fe',
+ '&yuml;': '\u00ff',
+ '&quot;': '\u0022',
+ '&lt;': '\u003c',
+ '&gt;': '\u003e',
+ '&apos;': '\u0027',
+ '&minus;': '\u2212',
+ '&circ;': '\u02c6',
+ '&tilde;': '\u02dc',
+ '&Scaron;': '\u0160',
+ '&lsaquo;': '\u2039',
+ '&OElig;': '\u0152',
+ '&lsquo;': '\u2018',
+ '&rsquo;': '\u2019',
+ '&ldquo;': '\u201c',
+ '&rdquo;': '\u201d',
+ '&bull;': '\u2022',
+ '&ndash;': '\u2013',
+ '&mdash;': '\u2014',
+ '&trade;': '\u2122',
+ '&scaron;': '\u0161',
+ '&rsaquo;': '\u203a',
+ '&oelig;': '\u0153',
+ '&Yuml;': '\u0178',
+ '&fnof;': '\u0192',
+ '&Alpha;': '\u0391',
+ '&Beta;': '\u0392',
+ '&Gamma;': '\u0393',
+ '&Delta;': '\u0394',
+ '&Epsilon;': '\u0395',
+ '&Zeta;': '\u0396',
+ '&Eta;': '\u0397',
+ '&Theta;': '\u0398',
+ '&Iota;': '\u0399',
+ '&Kappa;': '\u039a',
+ '&Lambda;': '\u039b',
+ '&Mu;': '\u039c',
+ '&Nu;': '\u039d',
+ '&Xi;': '\u039e',
+ '&Omicron;': '\u039f',
+ '&Pi;': '\u03a0',
+ '&Rho;': '\u03a1',
+ '&Sigma;': '\u03a3',
+ '&Tau;': '\u03a4',
+ '&Upsilon;': '\u03a5',
+ '&Phi;': '\u03a6',
+ '&Chi;': '\u03a7',
+ '&Psi;': '\u03a8',
+ '&Omega;': '\u03a9',
+ '&alpha;': '\u03b1',
+ '&beta;': '\u03b2',
+ '&gamma;': '\u03b3',
+ '&delta;': '\u03b4',
+ '&epsilon;': '\u03b5',
+ '&zeta;': '\u03b6',
+ '&eta;': '\u03b7',
+ '&theta;': '\u03b8',
+ '&iota;': '\u03b9',
+ '&kappa;': '\u03ba',
+ '&lambda;': '\u03bb',
+ '&mu;': '\u03bc',
+ '&nu;': '\u03bd',
+ '&xi;': '\u03be',
+ '&omicron;': '\u03bf',
+ '&pi;': '\u03c0',
+ '&rho;': '\u03c1',
+ '&sigmaf;': '\u03c2',
+ '&sigma;': '\u03c3',
+ '&tau;': '\u03c4',
+ '&upsilon;': '\u03c5',
+ '&phi;': '\u03c6',
+ '&chi;': '\u03c7',
+ '&psi;': '\u03c8',
+ '&omega;': '\u03c9',
+ '&thetasym;': '\u03d1',
+ '&upsih;': '\u03d2',
+ '&piv;': '\u03d6',
+ '&ensp;': '\u2002',
+ '&emsp;': '\u2003',
+ '&thinsp;': '\u2009',
+ '&zwnj;': '\u200c',
+ '&zwj;': '\u200d',
+ '&lrm;': '\u200e',
+ '&rlm;': '\u200f',
+ '&sbquo;': '\u201a',
+ '&bdquo;': '\u201e',
+ '&dagger;': '\u2020',
+ '&Dagger;': '\u2021',
+ '&hellip;': '\u2026',
+ '&permil;': '\u2030',
+ '&prime;': '\u2032',
+ '&Prime;': '\u2033',
+ '&oline;': '\u203e',
+ '&frasl;': '\u2044',
+ '&euro;': '\u20ac',
+ '&image;': '\u2111',
+ '&weierp;': '\u2118',
+ '&real;': '\u211c',
+ '&alefsym;': '\u2135',
+ '&larr;': '\u2190',
+ '&uarr;': '\u2191',
+ '&rarr;': '\u2192',
+ '&darr;': '\u2193',
+ '&harr;': '\u2194',
+ '&crarr;': '\u21b5',
+ '&lArr;': '\u21d0',
+ '&uArr;': '\u21d1',
+ '&rArr;': '\u21d2',
+ '&dArr;': '\u21d3',
+ '&hArr;': '\u21d4',
+ '&forall;': '\u2200',
+ '&part;': '\u2202',
+ '&exist;': '\u2203',
+ '&empty;': '\u2205',
+ '&nabla;': '\u2207',
+ '&isin;': '\u2208',
+ '&notin;': '\u2209',
+ '&ni;': '\u220b',
+ '&prod;': '\u220f',
+ '&sum;': '\u2211',
+ '&lowast;': '\u2217',
+ '&radic;': '\u221a',
+ '&prop;': '\u221d',
+ '&infin;': '\u221e',
+ '&ang;': '\u2220',
+ '&and;': '\u2227',
+ '&or;': '\u2228',
+ '&cap;': '\u2229',
+ '&cup;': '\u222a',
+ '&int;': '\u222b',
+ '&there4;': '\u2234',
+ '&sim;': '\u223c',
+ '&cong;': '\u2245',
+ '&asymp;': '\u2248',
+ '&ne;': '\u2260',
+ '&equiv;': '\u2261',
+ '&le;': '\u2264',
+ '&ge;': '\u2265',
+ '&sub;': '\u2282',
+ '&sup;': '\u2283',
+ '&nsub;': '\u2284',
+ '&sube;': '\u2286',
+ '&supe;': '\u2287',
+ '&oplus;': '\u2295',
+ '&otimes;': '\u2297',
+ '&perp;': '\u22a5',
+ '&sdot;': '\u22c5',
+ '&lceil;': '\u2308',
+ '&rceil;': '\u2309',
+ '&lfloor;': '\u230a',
+ '&rfloor;': '\u230b',
+ '&lang;': '\u2329',
+ '&rang;': '\u232a',
+ '&loz;': '\u25ca',
+ '&spades;': '\u2660',
+ '&clubs;': '\u2663',
+ '&hearts;': '\u2665',
+ '&diams;': '\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(/&amp;/g, '&');
+
+ return str;
+}
+
+exports.encode = function (str) {
+ str = str.replace(/&/g, '&amp;');
+
+ //IE doesn't accept &apos;
+ str = str.replace(/'/g, '&#39;');
+
+ //Encode literal entities
+ for (var i in entities) {
+ str = str.replace(new RegExp(entities[i], 'g'), i);
+ }
+
+ return str;
+}
View
102 node_modules/validator/lib/filter.js
@@ -0,0 +1,102 @@
+var entities = require('./entities');
+var xss = require('./xss');
+
+var Filter = exports.Filter = function() {}
+
+var whitespace = '\\r\\n\\t\\s';
+
+Filter.prototype.modify = function(str) {
+ this.str = str;
+}
+
+Filter.prototype.wrap = function (str) {
+ return str;
+}
+
+Filter.prototype.value = function () {
+ return this.str;
+}
+
+Filter.prototype.chain = function () {
+ this.wrap = function () { return this };
+ return this;
+}
+
+//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.wrap(this.str);
+}
+
+Filter.prototype.entityDecode = function() {
+ this.modify(entities.decode(this.str));
+ return this.wrap(this.str);
+}
+
+Filter.prototype.entityEncode = function() {
+ this.modify(entities.encode(this.str));
+ return this.wrap(this.str);
+}
+
+Filter.prototype.ltrim = function(chars) {
+ chars = chars || whitespace;
+ this.modify(this.str.replace(new RegExp('^['+chars+']+', 'g'), ''));
+ return this.wrap(this.str);
+}
+
+Filter.prototype.rtrim = function(chars) {
+ chars = chars || whitespace;
+ this.modify(this.str.replace(new RegExp('['+chars+']+$', 'g'), ''));
+ return this.wrap(this.str);
+}
+
+Filter.prototype.trim = function(chars) {
+ chars = chars || whitespace;
+ this.modify(this.str.replace(new RegExp('^['+chars+']+|['+chars+']+$', 'g'), ''));
+ return this.wrap(this.str);
+}
+
+Filter.prototype.ifNull = function(replace) {
+ if (!this.str || this.str === '') {
+ this.modify(replace);
+ }
+ return this.wrap(this.str);
+}
+
+Filter.prototype.toFloat = function() {
+ this.modify(parseFloat(this.str));
+ return this.wrap(this.str);
+}
+
+Filter.prototype.toInt = function(radix) {
+ radix = radix || 10;
+ this.modify(parseInt(this.str, radix));
+ return this.wrap(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.wrap(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.wrap(this.str);
+}
View
15 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);
+}
View
314 node_modules/validator/lib/validator.js
@@ -0,0 +1,314 @@
+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;
+ // Convert numbers to strings but keep arrays/objects
+ if (typeof this.str == 'number') {
+ this.str += '';
+ }
+ this.msg = fail_msg;
+ this._errors = this._errors || [];
+ return this;
+}
+
+// Helper function to avoid duplication of code
+function toDateTime(date) {
+ if (date instanceof Date) {
+ return date;
+ }
+ var intDate = Date.parse(date);
+ if (isNaN(intDate)) {
+ return null;
+ }
+ return new Date(intDate);
+}
+
+// Convert to date without the time component
+function toDate(date) {
+ if (!(date instanceof Date)) {
+ date = toDateTime(date);
+ }
+ if (!date) {
+ return null;
+ }
+ date.setHours(0);
+ date.setMinutes(0);
+ date.setSeconds(0);
+ date.setMilliseconds(0);
+ return date;
+}
+
+//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})*)?$/i)) {
+ 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-9][0-9]*)(?:\.?0+)?)$/)) {
+ return this.error(this.msg || 'Invalid integer');
+ }
+ return this;
+}
+
+Validator.prototype.isDecimal = function() {
+ if (this.str === '' || !this.str.match(/^(?:-?(?:[0-9]+))?(?:\.[0-9]*)?(?:[eE][\+\-]?(?:[0-9]+))?$/)) {
+ return this.error(this.msg || 'Invalid decimal');
+ }
+ return this;
+}
+
+Validator.prototype.isFloat = function() {
+ return this.isDecimal();
+}
+
+Validator.prototype.isDivisibleBy = function(n) {
+ if (parseFloat(this.str) % n) {
+ return this.error(this.msg || 'Not divisible by ' + n);
+ }
+ return this;
+}
+
+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 intDate = Date.parse(this.str);
+ if (isNaN(intDate)) {
+ return this.error(this.msg || 'Not a date');
+ }
+ return this;
+}
+
+Validator.prototype.isAfter = function(date) {
+ date = date || new Date();
+ var origDate = toDate(this.str);
+ var compDate = toDate(date);
+
+ if (origDate && compDate && origDate < compDate) {
+ return this.error(this.msg || 'Invalid date');
+ }
+
+ return this;
+}
+
+Validator.prototype.isBefore = function(date) {
+ date = date || new Date();
+ var origDate = toDate(this.str);
+ var compDate = toDate(date);
+
+ if (origDate && compDate && origDate > compDate) {
+ return this.error(this.msg || 'Invalid date');
+ }
+
+ return this;
+}
+
+Validator.prototype.isIn = 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');
+ }
+}
+
+Validator.prototype.min = function(val) {
+ var number = parseFloat(this.str);
+
+ if (!isNaN(number) && number < val) {
+ return this.error(this.msg || 'Invalid number');
+ }
+ return this;
+}
+
+Validator.prototype.max = function(val) {
+ var number = parseFloat(this.str);
+
+ if (!isNaN(number) && number > val) {
+ return this.error(this.msg || 'Invalid number');
+ }
+ return this;
+}
+
+Validator.prototype.isArray = function() {
+ if (!Array.isArray(this.str)) {
+ return this.error(this.msg || 'Not an array');
+ }
+ return this;
+}
+
+//Will work against Visa, MasterCard, American Express, Discover, Diners Club, and JCB card numbering formats
+Validator.prototype.isCreditCard = function() {
+ this.str = this.str.replace(/[^0-9]+/g, ''); //remove all dashes, spaces, etc.
+ if (!this.str.match(/^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/)) {
+ return this.error(this.msg || 'Invalid credit card');
+ }
+ return this;
+}
+
View
203 node_modules/validator/lib/xss.js
@@ -0,0 +1,203 @@
+//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]',
+ '<!--': '&lt;!--',
+ '-->': '--&gt;',
+ '<![CDATA[': '&lt;![CDATA['
+};
+
+var never_allowed_regex = {
+ 'javascript\\s*:': '[removed]',
+ 'expression\\s*(\\(|&\\#40;)': '[removed]',
+ 'vbscript\\s*:': '[removed]',
+ 'Redirect\\s+302': '[removed]'
+};
+
+var non_displayables = [
+ /%0[0-8bcef]/g, // url encoded 00-08, 11, 12, 14, 15
+ /%1[0-9a-f]/g, // url encoded 16-31
+ /[\x00-\x08]/g, // 00-08
+ /\x0b/g, /\x0c/g, // 11,12
+ /[\x0e-\x1f]/g // 14-31
+];
+
+var compact_words = [
+ 'javascript', 'expression', 'vbscript',
+ 'script', 'applet', 'alert', 'document',
+ 'write', 'cookie', 'window'
+];
+
+exports.clean = function(str, is_image) {
+
+ //Recursively clean objects and arrays
+ if (typeof str === 'array' || typeof str === 'object') {
+ for (var i in str) {
+ str[i] = exports.clean(str[i]);
+ }
+ return str;
+ }
+
+ //Remove invisible characters
+ str = remove_invisible_characters(str);
+
+ //Protect query string variables in URLs => 901119URL5918AMP18930PROTECT8198
+ str = str.replace(/\&([a-z\_0-9]+)\=([a-z\_0-9]+)/i, xss_hash() + '$1=$2');
+
+ //Validate standard character entities - add a semicolon if missing. We do this to enable
+ //the conversion of entities to ASCII later.
+ str = str.replace(/(&\#?[0-9a-z]{2,})([\x00-\x20])*;?/i, '$1;$2');
+
+ //Validate UTF16 two byte encoding (x00) - just as above, adds a semicolon if missing.
+ str = str.replace(/(&\#x?)([0-9A-F]+);?/i, '$1;$2');
+
+ //Un-protect query string variables
+ str = str.replace(xss_hash(), '&');
+
+ //Decode just in case stuff like this is submitted:
+ //<a href="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a>
+ try{
+ str = decodeURIComponent(str);
+ }
+ catch(error){
+ // str was not actually URI-encoded
+ }
+
+ //Convert character entities to ASCII - this permits our tests below to work reliably.
+ //We only convert entities that are within tags since these are the ones that will pose security problems.
+ str = str.replace(/[a-z]+=([\'\"]).*?\1/gi, function(m, match) {
+ return m.replace(match, convert_attribute(match));
+ });
+
+ //Remove invisible characters again
+ str = remove_invisible_characters(str);
+
+ //Convert tabs to spaces
+ str = str.replace('\t', ' ');
+
+ //Captured the converted string for later comparison
+ var converted_string = str;
+
+ //Remove strings that are never allowed
+ for (var i in never_allowed_str) {
+ str = str.replace(i, never_allowed_str[i]);
+ }
+
+ //Remove regex patterns that are never allowed
+ for (var i in never_allowed_regex) {
+ str = str.replace(new RegExp(i, 'i'), never_allowed_regex[i]);
+ }
+
+ //Compact any exploded words like: j a v a s c r i p t
+ // We only want to do this when it is followed by a non-word character
+ for (var i in compact_words) {
+ var spacified = compact_words[i].split('').join('\\s*')+'\\s*';
+
+ str = str.replace(new RegExp('('+spacified+')(\\W)', 'ig'), function(m, compat, after) {
+ return compat.replace(/\s+/g, '') + after;
+ });
+ }
+
+ //Remove disallowed Javascript in links or img tags
+ do {
+ var original = str;
+
+ if (str.match(/<a/i)) {
+ str = str.replace(/<a\s+([^>]*?)(>|$)/gi, function(m, attributes, end_tag) {
+ attributes = filter_attributes(attributes.replace('<','').replace('>',''));
+ return m.replace(attributes, attributes.replace(/href=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)/gi, ''));
+ });
+ }
+
+ if (str.match(/<img/i)) {
+ str = str.replace(/<img\s+([^>]*?)(\s?\/?>|$)/gi, function(m, attributes, end_tag) {
+ attributes = filter_attributes(attributes.replace('<','').replace('>',''));
+ return m.replace(attributes, attributes.replace(/src=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)/gi, ''));
+ });
+ }
+
+ if (str.match(/script/i) || str.match(/xss/i)) {
+ str = str.replace(/<(\/*)(script|xss)(.*?)\>/gi, '[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: <blink>
+ //Becomes: &lt;blink&gt;
+ 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 '&lt;' + a + b + c + d.replace('>','&gt;').replace('<','&lt;');
+ });
+
+ //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&#40;'some code'&#41;
+ str = str.replace(/(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)/gi, '$1$2&#40;$3&#41;');
+
+ //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('>','&gt;').replace('<','&lt;').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;
+}
View
65 node_modules/validator/package.json
@@ -0,0 +1,65 @@
+{
+ "name": "validator",
+ "description": "Data validation, filtering and sanitization for node.js",
+ "version": "0.3.7",
+ "homepage": "http://github.com/chriso/node-validator",
+ "keywords": [
+ "validator",
+ "validation",
+ "assert",
+ "params",
+ "sanitization",
+ "xss",
+ "entities",
+ "sanitize",
+ "sanitisation",
+ "input"
+ ],
+ "author": {
+ "name": "Chris O'Hara",
+ "email": "cohara87@gmail.com"
+ },
+ "main": "./lib",
+ "directories": {
+ "lib": "./lib"
+ },
+ "bugs": {
+ "url": "http://github.com/chriso/node-validator/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/chriso/node-validator.git"
+ },
+ "contributors": [
+ {
+ "name": "PING"
+ },
+ {
+ "name": "Dan VerWeire"
+ },
+ {
+ "name": "Branko Vukelic"
+ }
+ ],
+ "engines": {
+ "node": ">=0.2.2"
+ },
+ "licenses": [
+ {
+ "type": "MIT",
+ "url": "http://github.com/chriso/node-validator/raw/master/LICENSE"
+ }
+ ],
+ "_id": "validator@0.3.7",
+ "dependencies": {},
+ "devDependencies": {},
+ "optionalDependencies": {},
+ "_engineSupported": true,
+ "_npmVersion": "1.1.24",
+ "_nodeVersion": "v0.6.19",
+ "_defaultsLoaded": true,
+ "dist": {
+ "shasum": "2388303cbf67c024f21b0208989a99b321cfebbe"
+ },
+ "_from": "validator@0.3.7"
+}
View
4 node_modules/validator/test.js
@@ -0,0 +1,4 @@
+
+var xss = require('./validator').xssClean;
+
+console.log(xss('I <3 this'));
View
145 node_modules/validator/test/filter.test.js
@@ -0,0 +1,145 @@
+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('&amp;').entityDecode());
+ assert.equal('&&', Filter.sanitize('&amp;&amp;').entityDecode());
+ assert.equal('""', Filter.sanitize('&quot;&quot;').entityDecode());
+ assert.equal('', Filter.sanitize('&curren;').entityDecode());
+ assert.equal("'", Filter.sanitize("&#39;").entityDecode());
+ assert.equal("'", Filter.sanitize("&apos;").entityDecode());
+ },
+
+ 'test #entityEncode()': function () {
+ assert.equal('&amp;', Filter.sanitize('&').entityEncode());
+ assert.equal('&amp;&amp;', Filter.sanitize('&&').entityEncode());
+ assert.equal('&#39;', Filter.sanitize("'").entityEncode());
+ assert.equal('&quot;&quot;', Filter.sanitize('""').entityEncode());
+ assert.equal('&curren;', 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());
+ },
+
+ 'test chaining': function () {
+ assert.equal('&amp;amp;amp;', Filter.sanitize('&').chain().entityEncode().entityEncode().entityEncode().value());
+
+ //Return the default behaviour
+ Filter.wrap = function (str) {
+ return str;
+ }
+ }
+
+}
View
8 node_modules/validator/test/run.js
@@ -0,0 +1,8 @@
+var validatorTests = require('./validator.test.js');
+for (test in validatorTests) {
+ validatorTests[test]();
+}
+var filterTests = require('./filter.test.js');
+for (test in filterTests) {
+ filterTests[test]();
+}
View
604 node_modules/validator/test/validator.test.js
@@ -0,0 +1,604 @@
+var node_validator = require('../lib'),
+ Validator = new node_validator.Validator(),
+ assert = require('assert');
+
+function dateFixture() {
+ var d = new Date();
+ var Y = d.getFullYear();
+ var M = d.getMonth() + 1; // 0-index
+ var D = d.getDate();
+ return {
+ tomorrow: Y + '-' + M + '-' + (D + 1), // YYYY-MM-DD
+ yesterday: Y + '-' + M + '-' + (D - 1) // YYYY-MM-DD
+ };
+}
+
+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/',
+ 'http://duckduckgo.com/?q=%2F'
+ ];
+ 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');