├── Changes.md ├── LICENSE ├── README.md ├── dist.ini ├── lib └── resty │ ├── validation.lua │ └── validation │ ├── injection.lua │ ├── ngx.lua │ ├── tz.lua │ └── utf8.lua └── lua-resty-validation-dev-1.rockspec /Changes.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `lua-resty-validation` will be documented in this file. 4 | 5 | ## [2.7] - 2017-08-25 6 | ### Added 7 | - Added callable group validator, thanks @hishamhm 8 | 9 | ## [2.6] - 2017-02-05 10 | ### Added 11 | - Added callable validator 12 | - Added requisite and requisites group validators 13 | (see also: https://github.com/bungle/lua-resty-validation/issues/3) 14 | 15 | ## [2.5] - 2016-09-29 16 | ### Added 17 | - Support for the official OpenResty package manager (opm). 18 | 19 | ### Changed 20 | - Changed the change log format to keep-a-changelog. 21 | 22 | ## [2.4] - 2016-09-16 23 | ### Added 24 | - Added support for custom (inline) validators. 25 | - Added resty.validation.injection extension (uses libinjection). 26 | 27 | ## [2.3] - 2016-03-22 28 | ### Added 29 | - Added resty.validation.utf8 extension (uses utf8rewind). 30 | 31 | ## [2.2] - 2015-11-27 32 | ### Fixed 33 | - There was a typo in a code that leaked a global variable in fields:__call method. 34 | 35 | ## [2.1] - 2015-10-10 36 | ### Fixed 37 | - Fixed leaking global new function. 38 | 39 | ## [2.1] - 2015-10-10 40 | ### Changed 41 | - Total rewrite. 42 | 43 | ## [1.0] - 2014-08-28 44 | ### Added 45 | - LuaRocks support via MoonRocks. 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 - 2017, Aapo Talvensaari 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-resty-validation 2 | 3 | **lua-resty-validation** is an extendable chaining validation and filtering library for Lua and OpenResty. 4 | 5 | ## Hello World with lua-resty-validation 6 | 7 | ```lua 8 | local validation = require "resty.validation" 9 | 10 | local valid, e = validation.number:between(0, 9)(5) -- valid = true, e = 5 11 | local valid, e = validation.number:between(0, 9)(50) -- valid = false, e = "between" 12 | 13 | -- Validators can be reused 14 | local smallnumber = validation.number:between(0, 9) 15 | local valid, e = smallnumber(5) -- valid = true, e = 5 16 | local valid, e = smallnumber(50) -- valid = false, e = "between" 17 | 18 | -- Validators can do filtering (i.e. modify the value being validated) 19 | -- valid = true, s = "HELLO WORLD!" 20 | local valid, s = validation.string.upper "hello world!" 21 | 22 | -- You may extend the validation library with your own validators and filters... 23 | validation.validators.capitalize = function(value) 24 | return true, value:gsub("^%l", string.upper) 25 | end 26 | 27 | -- ... and then use it 28 | local valid, e = validation.capitalize "abc" -- valid = true, e = "Abc" 29 | 30 | -- You can also group validate many values 31 | local group = validation.new{ 32 | artist = validation.string:minlen(5), 33 | number = validation.tonumber:equal(10) 34 | } 35 | 36 | local valid, fields, errors = group{ artist = "Eddie Vedder", number = "10" } 37 | 38 | if valid then 39 | print("all the group fields are valid") 40 | else 41 | print(fields.artist.name, fields.artist.error, 42 | fields.artist.valid, fields.artist.invalid, 43 | fields.artist.input, fields.artist.value, , 44 | fields.artist.validated, fields.artist.unvalidated) 45 | end 46 | 47 | -- You can even call fields to get simple name, value table 48 | -- (in that case all the `nil`s are removed as well) 49 | 50 | -- By default this returns only the valid fields' names and values: 51 | local data = fields() 52 | local data = fields "valid" 53 | 54 | -- To get only the invalid fields' names and values call: 55 | local data = fields "invalid" 56 | 57 | -- To get only the validated fields' names and values call (whether or not they are valid): 58 | local data = fields "validated" 59 | 60 | -- To get only the unvalidated fields' names and values call (whether or not they are valid): 61 | local data = fields "unvalidated" 62 | 63 | -- To get all, call: 64 | local data = fields "all" 65 | 66 | -- Or combine: 67 | local data = fields("valid", "invalid") 68 | 69 | -- This doesn't stop here. You may also want to get only some fields by their name. 70 | -- You can do that by calling (returns a table): 71 | local data = data("artist") 72 | ``` 73 | 74 | ## Installation 75 | 76 | Just place [`validation.lua`](https://github.com/bungle/lua-resty-validation/blob/master/lib/resty/validation.lua) and [`validation`](https://github.com/bungle/lua-resty-template/tree/master/lib/resty/validation) directory somewhere in your `package.path`, under `resty` directory. If you are using OpenResty, the default location would be `/usr/local/openresty/lualib/resty`. 77 | 78 | ### Using OpenResty Package Manager (opm) 79 | 80 | ```Shell 81 | $ opm get bungle/lua-resty-validation 82 | ``` 83 | 84 | ### Using LuaRocks 85 | 86 | ```Shell 87 | $ luarocks install lua-resty-validation 88 | ``` 89 | 90 | LuaRocks repository for `lua-resty-validation` is located at https://luarocks.org/modules/bungle/lua-resty-validation. 91 | 92 | ## Built-in Validators and Filters 93 | 94 | `lua-resty-validation` comes with several built-in validators, and the project is open for contributions of more validators. 95 | 96 | ### Validators and Filters without Arguments 97 | 98 | Type validators can be used to validate the type of the validated value. These validators are argument-less 99 | validators (call them with dot `.`): 100 | 101 | * `null` or `["nil"]` (as the nil is a reserved keyword in Lua) 102 | * `boolean` 103 | * `number` 104 | * `string` 105 | * `table` 106 | * `userdata` 107 | * `func` or `["function"]` (as the function is a reserved keyword in Lua) 108 | * `callable` (either a function or a table with metamethod `__call`) 109 | * `thread` 110 | * `integer` 111 | * `float` 112 | * `file` (`io.type(value) == 'file'`) 113 | 114 | Type conversion filters: 115 | 116 | * `tostring` 117 | * `tonumber` 118 | * `tointeger` 119 | * `toboolean` 120 | 121 | Other filters: 122 | 123 | * `tonil` or `tonull` 124 | * `abs` 125 | * `inf` 126 | * `nan` 127 | * `finite` 128 | * `positive` 129 | * `negative` 130 | * `lower` 131 | * `upper` 132 | * `trim` 133 | * `ltrim` 134 | * `rtrim` 135 | * `reverse` 136 | * `email` 137 | * `optional` 138 | 139 | #### Example 140 | 141 | ```lua 142 | local validation = require "resty.validation" 143 | local ok, e = validation.null(nil) 144 | local ok, e = validation.boolean(true) 145 | local ok, e = validation.number(5.2) 146 | local ok, e = validation.string('Hello, World!') 147 | local ok, e = validation.integer(10) 148 | local ok, e = validation.float(math.pi) 149 | local f = assert(io.open('filename.txt', "r")) 150 | local ok, e = validation.file(f) 151 | ``` 152 | 153 | ### Validation Factory Validators and Filters 154 | 155 | Validation factory consist of different validators and filters used to validate or filter the value 156 | (call them with colon `:`): 157 | 158 | * `type(t)`, validates that the value is of type `t` (see Type Validators) 159 | * `nil()` or `["null"]()`, check that value type is `nil` 160 | * `boolean()`, check that value type is `boolean` 161 | * `number()`, check that value type is `number` 162 | * `string()`, check that value type is `string` 163 | * `table()`, check that value type is `table` 164 | * `userdata()`, check that value type is `userdata` 165 | * `func()` or `["function"]()`, check that value type is `function` 166 | * `callable()`, check that value is callable (aka a function or a table with metamethod `__call`) 167 | * `thread()`, check that value type is `thread` 168 | * `integer()`, check that value type is `integer` 169 | * `float()`, check that value type is `float` 170 | * `file()`, check that value type is `file` (`io.type(value) == 'file'`) 171 | * `abs()`, filters value and returns absolute value (`math.abs`) 172 | * `inf()`, checks that the value is `inf` or `-inf` 173 | * `nan()`, checks that the value is `nan` 174 | * `finite()`, checks that the value is not `nan`, `inf` or `-inf` 175 | * `positive()`, validates that the value is positive (`> 0`) 176 | * `negative()`, validates that the value is negative (`< 0`) 177 | * `min(min)`, validates that the value is at least `min` (`>=`) 178 | * `max(max)`, validates that the value is at most `max` (`<=`) 179 | * `between(min[, max = min])`, validates that the value is between `min` and `max` 180 | * `outside(min[, max = min])`, validates that the value is not between `min` and `max` 181 | * `divisible(number)`, validates that the value is divisible with `number` 182 | * `indivisible(number)`, validates that the value is not divisible with `number` 183 | * `len(min[, max = min])`, validates that the length of the value is exactly `min` or between `min` and `max` (UTF-8) 184 | * `minlen(min)`, validates that the length of the value is at least `min` (UTF-8) 185 | * `maxlen(max)`, validates that the length of the value is at most `max` (UTF-8) 186 | * `equals(equal)` or `equal(equal)`, validates that the value is exactly something 187 | * `unequals(equal)` or `unequal(equal)`, validates that the value is not exactly something 188 | * `oneof(...)`, validates that the value is equal to one of the supplied arguments 189 | * `noneof(...)`, validates that the value is not equal to any of the supplied arguments 190 | * `match(pattern[, init])`, validates that the value matches (`string.match`) the pattern 191 | * `unmatch(pattern[, init])`, validates that the value does not match (`string.match`) the pattern 192 | * `tostring()`, converts value to string 193 | * `tonumber([base])`, converts value to number 194 | * `tointeger()`, converts value to integer 195 | * `toboolean()`, converts value to boolean (using `not not value`) 196 | * `tonil()` or `tonull()`, converts value to nil 197 | * `lower()`, converts value to lower case (UTF-8 support is not yet implemented) 198 | * `upper()`, converts value to upper case (UTF-8 support is not yet implemented) 199 | * `trim([pattern])`, trims whitespace (you may use pattern as well) from the left and the right 200 | * `ltrim([pattern])`, trims whitespace (you may use pattern as well) from the left 201 | * `rtrim([pattern])`, trims whitespace (you may use pattern as well) from the right 202 | * `starts(starts)`, checks if string starts with `starts` 203 | * `ends(ends)`, checks if string ends with `ends` 204 | * `reverse`, reverses the value (string or number) (UTF-8) 205 | * `coalesce(...)`, if the value is nil, returns first non-nil value passed as arguments 206 | * `email()`, validates that the value is email address 207 | * `call(function)`, validates / filters the value against custom inline validator / filter 208 | * `optional([default])`, stops validation if the value is empty string `""` or `nil` and returns `true`, and either, `default` or `value` 209 | 210 | #### Conditional Validation Factory Validators 211 | 212 | For all the Validation Factory Validators there is a conditional version that always validates to true, 213 | but where you can replace the actual value depending whether the original validator validated. Hey, this 214 | is easier to show than say: 215 | 216 | ```lua 217 | local validation = require "resty.validation" 218 | 219 | -- ok == true, value == "Yes, the value is nil" 220 | local ok, value = validation:ifnil( 221 | "Yes, the value is nil", 222 | "No, you did not supply a nil value")(nil) 223 | 224 | -- ok == true, value == "No, you did not supply a nil value" 225 | local ok, value = validation:ifnil( 226 | "Yes, the value is nil", 227 | "No, you did not supply a nil value")("non nil") 228 | 229 | -- ok == true, value == "Yes, the number is betweeb 1 and 10" 230 | local ok, value = validation:ifbetween(1, 10, 231 | "Yes, the number is between 1 and 10", 232 | "No, the number is not between 1 and 10")(5) 233 | 234 | -- ok == true, value == "No, the number is not between 1 and 10" 235 | local ok, value = validation:ifbetween(1, 10, 236 | "Yes, the number is between 1 and 10", 237 | "No, the number is not between 1 and 10")(100) 238 | ``` 239 | 240 | The last 2 arguments to conditional validation factory validators are the `truthy` and `falsy` values. 241 | Every other argument is passed to the actual validation factory validator. 242 | 243 | ### Group Validators 244 | 245 | `lua-resty-validation` currently supports a few predefined validators: 246 | 247 | * `compare(comparison)`, compares two fields and sets fields invalid or valid according to comparison 248 | * `requisite{ fields }`, at least of of the requisite fields is required, even if they by themselves are optional 249 | * `requisites({ fields }, number)`, at least `number` of requisites fields are required (by default all of them) 250 | * `call(function)`, calls a custom (or inline) group validation function 251 | 252 | ```lua 253 | local ispassword = validation.trim:minlen(8) 254 | local group = validation.new{ 255 | password1 = ispassword, 256 | password2 = ispassword 257 | } 258 | group:compare "password1 == password2" 259 | local valid, fields, errors = group{ password1 = "qwerty123", password2 = "qwerty123" } 260 | 261 | local optional = validation:optional"".trim 262 | local group = validation.new{ 263 | text = optional, 264 | html = optional 265 | } 266 | group:requisite{ "text", "html" } 267 | local valid, fields, errors = group{ text = "", html = "" } 268 | 269 | 270 | local optional = validation:optional "" 271 | local group = validation.new{ 272 | text = optional, 273 | html = optional 274 | } 275 | group:requisites({ "text", "html" }, 2) 276 | -- or group:requisites{ "text", "html" } 277 | local valid, fields, errors = group{ text = "", html = "" } 278 | 279 | 280 | group:call(function(fields) 281 | if fields.text.value == "hello" then 282 | fields.text:reject "text cannot be 'hello'" 283 | fields.html:reject "because text was 'hello', this field is also invalidated" 284 | end 285 | end) 286 | ``` 287 | 288 | You can use normal Lua relational operators in `compare` group validator: 289 | 290 | * `<` 291 | * `>` 292 | * `<=` 293 | * `>=` 294 | * `==` 295 | * `~=` 296 | 297 | `requisite` and `requisites` check if the field value is `nil` or `""`(empty string). 298 | With `requisite`, if all the specified fields are `nil` or `""` then all the fields are 299 | invalid (provided they were not by themselves invalid), and if at least one of the fields 300 | is valid then all the fields are valid. `requisites` works the same, but there you can 301 | define the number of how many fields you want to have a value that is not `nil` and not 302 | an empty string `""`. These provide conditional validation in sense of: 303 | 304 | 1. I have (two or more) fields 305 | 2. All of them are optional 306 | 3. At least one / defined number of fields should be filled but I don't care which one as long as there is at least one / defined number of fields filled 307 | 308 | ### Stop Validators 309 | 310 | Stop validators, like `optional`, are just like a normal validators, but instead of returning 311 | `true` or `false` as a validation result OR as a filtered value, you can return `validation.stop`. 312 | This value can also be used inside conditional validators and in validators that support default values. Here is how 313 | the `optional` validator is implemented: 314 | 315 | ```lua 316 | function factory.optional(default) 317 | return function(value) 318 | if value == nil or value == "" then 319 | return validation.stop, default ~= nil and default or value 320 | end 321 | return true, value 322 | end 323 | end 324 | ``` 325 | 326 | These are roughly equivalent: 327 | 328 | ```lua 329 | -- Both return: true, "default" (they stop prosessing :minlen(10) on nil and "" inputs 330 | local input = "" 331 | local ok, val = validation.optional:minlen(10)(input) 332 | local ok, val = validation:optional(input):minlen(10)(input) 333 | local ok, val = validation:ifoneof("", nil, validation.stop(input), input):minlen(10)(input) 334 | ``` 335 | 336 | ### Filtering Value and Setting the Value to `nil` 337 | 338 | Most of the validators, that are not filtering the value, only return `true` or `false` as a result. 339 | That means that there is now no way to signal `resty.validation` to actually set the value to `nil`. 340 | So there is a work-around, you can return `validation.nothing` as a value, and that will change the 341 | value to `nil`, e.g. the built-in `tonil` validator is actually implemented like this (pseudo): 342 | 343 | ```lua 344 | function() 345 | return true, validation.nothing 346 | end 347 | ``` 348 | 349 | ### Custom (Inline) Validators and Filters 350 | 351 | Sometimes you may just have one-off validators / filters that you are not using elsewhere, or that you just 352 | want to supply quickly an additional validator / filter for a specific case. To make that easy and straight 353 | forward, we introduced `call` factory method with `lua-resty-validation` 2.4. Here is an example: 354 | 355 | ```lua 356 | validation:call(function(value) 357 | -- now validate / filter the value, and return the results 358 | -- here we just return false (aka making validation to fail) 359 | return false 360 | end)("Check this value")) 361 | ``` 362 | 363 | (of course it doesn't need to be inline function as in Lua all functions are first class citizens and they can 364 | be passed around as parameters) 365 | 366 | ### Built-in Validator Extensions 367 | 368 | Currently `lua-resty-validation` has support for two extensions or plugins that you can enable: 369 | 370 | * `resty.validation.ngx` 371 | * `resty.validation.tz` 372 | * `resty.validation.utf8` 373 | 374 | These are something you can look at if you want to build your own validator extension. If you do 375 | so, and think that it would be usable for others as well, mind you to send your extension as a pull-request 376 | for inclusion in this project, thank you very much, ;-). 377 | 378 | #### resty.validation.ngx extension 379 | 380 | As the name tells, this set of validator extensions requires OpenResty (or Lua Nginx module at least). 381 | To use this extension all you need to do is: 382 | 383 | ```lua 384 | require "resty.validation.ngx" 385 | ``` 386 | 387 | It will monkey patch the adapters that it will provide in `resty.validation`, and those are currently: 388 | 389 | * `escapeuri` 390 | * `unescapeuri` 391 | * `base64enc` 392 | * `base64dec` 393 | * `crc32short` 394 | * `crc32long` 395 | * `crc32` 396 | * `md5` 397 | 398 | (there is both factory and argument-less version of these) 399 | 400 | There is also regex matcher in ngx extension that uses `ngx.re.match`, and parameterized `md5`: 401 | 402 | * `regex(regex[, options])` 403 | * `md5([bin])` 404 | 405 | ##### Example 406 | 407 | ```lua 408 | require "resty.validation.ngx" 409 | local validation = require "resty.validation" 410 | local valid, value = validation.unescapeuri.crc32("https://github.com/") 411 | local valid, value = validation:unescapeuri():crc32()("https://github.com/") 412 | ``` 413 | 414 | #### resty.validation.tz extension 415 | 416 | This set of validators and filters is based on the great [`luatz`](https://github.com/daurnimator/luatz) 417 | library by [@daurnimator](https://github.com/daurnimator), that is a library for time and date manipulation. To use this extension, all you need 418 | to do is: 419 | 420 | ```lua 421 | require "resty.validation.tz" 422 | ``` 423 | 424 | It will monkey patch the adapters that it will provide in `resty.validation`, and those are currently: 425 | 426 | * `totimetable` 427 | * `totimestamp` 428 | 429 | (there is both factory and argument-less version of these) 430 | 431 | `totimestamp` and `totimetable` filters work great with HTML5 date and datetime input fields. As the name 432 | tells, `totimetable` returns luatz `timetable` and `totimestamp` returns seconds since unix epoch (`1970-01-01`) 433 | as a Lua number. 434 | 435 | ##### Example 436 | 437 | ```lua 438 | require "resty.validation.tz" 439 | local validation = require "resty.validation" 440 | local valid, ts = validation.totimestamp("1990-12-31T23:59:60Z") 441 | local valid, ts = validation.totimestamp("1996-12-19") 442 | ``` 443 | 444 | #### resty.validation.utf8 extension 445 | 446 | This set of validators and filters is based on the great [`utf8rewind`](https://bitbucket.org/knight666/utf8rewind) 447 | library by Quinten Lansu - a system library written in C designed to extend the default string handling functions 448 | with support for UTF-8 encoded text. It needs my LuaJIT FFI wrapper [`lua-resty-utf8rewind`](https://github.com/bungle/lua-resty-utf8rewind) 449 | to work. When the mentioned requirements are installed, the rest is easy. To use this extension, all you need 450 | to do is: 451 | 452 | ```lua 453 | require "resty.validation.utf8" 454 | ``` 455 | 456 | It will monkey patch the adapters that it will provide in `resty.validation`, and those are currently: 457 | 458 | * `utf8upper` 459 | * `utf8lower` 460 | * `utf8title` 461 | 462 | (there is both factory and argument-less version of these) 463 | 464 | There is also a few factory validators / filters: 465 | 466 | * `utf8normalize(form)` 467 | * `utf8category(category)` 468 | 469 | The `utf8normalize` normalizes the UTF-8 input to one of these normalization formats: 470 | 471 | * `C` (or `NFC`) 472 | * `D` (or `NFD`) 473 | * `KC` (or `NFKC`) 474 | * `KD` (or `NFKD`) 475 | 476 | The `utf8category` checks that the input string is in one of the following categories (so, you may think it has 477 | multiple validators built-in to work with UTF-8 string validation): 478 | 479 | * `LETTER_UPPERCASE` 480 | * `LETTER_LOWERCASE` 481 | * `LETTER_TITLECASE` 482 | * `LETTER_MODIFIER` 483 | * `CASE_MAPPED` 484 | * `LETTER_OTHER` 485 | * `LETTER` 486 | * `MARK_NON_SPACING` 487 | * `MARK_SPACING` 488 | * `MARK_ENCLOSING` 489 | * `MARK` 490 | * `NUMBER_DECIMAL` 491 | * `NUMBER_LETTER` 492 | * `NUMBER_OTHER` 493 | * `NUMBER` 494 | * `PUNCTUATION_CONNECTOR` 495 | * `PUNCTUATION_DASH` 496 | * `PUNCTUATION_OPEN` 497 | * `PUNCTUATION_CLOSE` 498 | * `PUNCTUATION_INITIAL` 499 | * `PUNCTUATION_FINAL` 500 | * `PUNCTUATION_OTHER` 501 | * `PUNCTUATION` 502 | * `SYMBOL_MATH` 503 | * `SYMBOL_CURRENCY` 504 | * `SYMBOL_MODIFIER` 505 | * `SYMBOL_OTHER` 506 | * `SYMBOL` 507 | * `SEPARATOR_SPACE` 508 | * `SEPARATOR_LINE` 509 | * `SEPARATOR_PARAGRAPH` 510 | * `SEPARATOR` 511 | * `CONTROL` 512 | * `FORMAT` 513 | * `SURROGATE` 514 | * `PRIVATE_USE` 515 | * `UNASSIGNED` 516 | * `COMPATIBILITY` 517 | * `ISUPPER` 518 | * `ISLOWER` 519 | * `ISALPHA` 520 | * `ISDIGIT` 521 | * `ISALNUM` 522 | * `ISPUNCT` 523 | * `ISGRAPH` 524 | * `ISSPACE` 525 | * `ISPRINT` 526 | * `ISCNTRL` 527 | * `ISXDIGIT` 528 | * `ISBLANK` 529 | * `IGNORE_GRAPHEME_CLUSTER` 530 | 531 | ##### Example 532 | 533 | ```lua 534 | require "resty.validation.utf8" 535 | local validation = require "resty.validation" 536 | local valid, ts = validation:utf8category("LETTER_UPPERCASE")("TEST") 537 | ``` 538 | 539 | #### resty.validation.injection extension 540 | 541 | This set of validators and filters is based on the great [`libinjection`](https://github.com/client9/libinjection) 542 | library by Nick Galbreath - a SQL / SQLI / XSS tokenizer parser analyzer. It needs my LuaJIT FFI wrapper 543 | [`lua-resty-injection`](https://github.com/bungle/lua-resty-injection) to work. When the mentioned requirements 544 | are installed, the rest is easy. To use this extension, all you need to do is: 545 | 546 | ```lua 547 | require "resty.validation.injection" 548 | ``` 549 | 550 | It will monkey patch the adapters that it will provide in `resty.validation`, and those are currently: 551 | 552 | * `sqli`, returns `false` if SQL injection was detected, otherwise returns `true` 553 | * `xss`, returns `false` if Cross-Site Scripting injection was detected, otherwise returns `true` 554 | 555 | ##### Example 556 | 557 | ```lua 558 | require "resty.validation.injection" 559 | local validation = require "resty.validation" 560 | local valid, ts = validation.sqli("test'; DELETE FROM users;") 561 | local valid, ts = validation.xss("test ") 562 | ``` 563 | 564 | ## API 565 | 566 | I'm not going here for details for all the different validators and filters there is because they all follow the 567 | same logic, but I will show some general ways how this works. 568 | 569 | ### validation._VERSION 570 | 571 | This field contains a version of the validation library, e.g. it's value can be `"2.5"` for 572 | the version 2.5 of this library. 573 | 574 | ### boolean, value/error validation... 575 | 576 | That `...` means the validation chain. This is used to define a single validator chain. There is no limit to 577 | chain length. It will always return boolean (if the validation is valid or not). The second return value will 578 | be either the name of the filter that didn't return `true` as a validation result, or the filtered value. 579 | 580 | ```lua 581 | local v = require "resty.validation" 582 | 583 | -- The below means, create validator that checks that the input is: 584 | -- 1. string 585 | -- If, it is, then trim whitespaces from begin and end of the string: 586 | -- 2. trim 587 | -- Then check that the trimmed string's length is at least 5 characters (UTF-8): 588 | -- 3. minlen(5) 589 | -- And if everything is still okay, convert that string to upper case 590 | -- (UTF-8 is not yet supported in upper): 591 | -- 4. upper 592 | local myvalidator = v.string.trim:minlen(5).upper 593 | 594 | -- This example will return false and "minlen" 595 | local valid, value = myvalidator(" \n\t a \t\n ") 596 | 597 | -- This example will return true and "ABCDE" 598 | local valid, value = myvalidator(" \n\t abcde \t\n ") 599 | ``` 600 | 601 | Whenever the validator fails and returns `false`, you should not use the returned value for other purposes than 602 | error reporting. So, the chain works like that. The `lua-resty-validation` will not try to do anything if you 603 | specify chains that will never get used, such as: 604 | 605 | ```lua 606 | local v = require "resty.validation" 607 | -- The input value can never be both string and number at the same time: 608 | local myvalidator = v.string.number:max(3) 609 | -- But you could write this like this 610 | -- (take input as a string, try to convert it to number, and check it is at most 3): 611 | local myvalidator = v.string.tonumber:max(3) 612 | ``` 613 | 614 | As you see, this is a way to define single reusable validators. You can for example predefine your set of basic 615 | single validator chains and store it in your own module from which you can reuse the same validation logic in 616 | different parts of your application. It is good idea to start defining single reusable validators, and then reuse 617 | them in group validators. 618 | 619 | E.g. say you have module called `validators`: 620 | 621 | ```lua 622 | local v = require "resty.validation" 623 | return { 624 | nick = v.string.trim:minlen(2), 625 | email = v.string.trim.email, 626 | password = v.string.trim:minlen(8) 627 | } 628 | ``` 629 | 630 | And now you have `register` function somewhere in your application: 631 | 632 | ```lua 633 | local validate = require "validators" 634 | local function register(nick, email, password) 635 | local vn, nick = validate.nick(nick) 636 | local ve, email = validate.email(email) 637 | local vp, password = validate.password(password) 638 | if vn and ve and vp then 639 | -- input is valid, do something with nick, email, and password 640 | else 641 | -- input is invalid, nick, email, and password contain the error reasons 642 | end 643 | end 644 | ``` 645 | 646 | This quickly gets a little bit dirty, and that's why we have Group validators. 647 | 648 | ### table validation.new([table of validators]) 649 | 650 | This function is where the group validation kicks in. Say that you have a registration 651 | form that asks you nick, email (same twice), and password (same twice). 652 | 653 | We will reuse the single validators, defined in `validators` module: 654 | 655 | ```lua 656 | local v = require "resty.validation" 657 | return { 658 | nick = v.string.trim:minlen(2), 659 | email = v.string.trim.email, 660 | password = v.string.trim:minlen(8) 661 | } 662 | ``` 663 | 664 | Now, lets create the reusable group validator in `forms` module: 665 | 666 | ```lua 667 | local v = require "resty.validation" 668 | local validate = require "validators" 669 | 670 | -- First we create single validators for each form field 671 | local register = v.new{ 672 | nick = validate.nick, 673 | email = validate.email, 674 | email2 = validate.email, 675 | password = validate.password, 676 | password2 = validate.password 677 | } 678 | 679 | -- Next we create group validators for email and password: 680 | register:compare "email == email2" 681 | register:compare "password == password2" 682 | 683 | -- And finally we return from this forms module 684 | 685 | return { 686 | register = register 687 | } 688 | 689 | ``` 690 | 691 | Now, somewhere in your application you have this `register` function: 692 | 693 | 694 | ```lua 695 | local forms = require "forms" 696 | local function register(data) 697 | local valid, fields, errors = forms.register(data) 698 | if valid then 699 | -- input is valid, do something with fields 700 | else 701 | -- input is invalid, do something with fields and errors 702 | end 703 | end 704 | 705 | -- And you might call it like: 706 | 707 | register{ 708 | nick = "test", 709 | email = "test@test.org", 710 | email2 = "test@test.org", 711 | password = "qwerty123", 712 | password2 = "qwerty123" 713 | } 714 | 715 | ``` 716 | 717 | The great thing about group validators is that you can JSON encode the fields and errors 718 | table and return it to client. This might come handy when building a single page application 719 | and you need to report server side errors on client. In the above example, the `fields` 720 | variable will look like this (`valid` would be true:, and `errors` would be `nil`): 721 | 722 | ```lua 723 | { 724 | nick = { 725 | unvalidated = false, 726 | value = "test", 727 | input = "test", 728 | name = "nick", 729 | valid = true, 730 | invalid = false, 731 | validated = true 732 | }, 733 | email = { 734 | unvalidated = false, 735 | value = "test@test.org", 736 | input = "test@test.org", 737 | name = "email", 738 | valid = true, 739 | invalid = false, 740 | validated = true 741 | }, 742 | email2 = { 743 | unvalidated = false, 744 | value = "test@test.org", 745 | input = "test@test.org", 746 | name = "email2", 747 | valid = true, 748 | invalid = false, 749 | validated = true 750 | }, 751 | password = { 752 | unvalidated = false, 753 | value = "qwerty123", 754 | input = "qwerty123", 755 | name = "password", 756 | valid = true, 757 | invalid = false, 758 | validated = true 759 | }, 760 | password2 = { 761 | unvalidated = false, 762 | value = "qwerty123", 763 | input = "qwerty123", 764 | name = "password2", 765 | valid = true, 766 | invalid = false, 767 | validated = true 768 | } 769 | } 770 | ``` 771 | 772 | This is great for further processing and sending the fields as JSON encoded back 773 | to the client-side Javascript application, but usually this is too heavy construct 774 | to be send to the backend layer. To get a simple key value table, we can call this 775 | fields table: 776 | 777 | ```lua 778 | local data = fields() 779 | ``` 780 | 781 | The `data` variable will now contain: 782 | 783 | ```lua 784 | { 785 | nick = "test", 786 | email = "test@test.org", 787 | email2 = "test@test.org", 788 | password = "qwerty123", 789 | password2 = "qwerty123" 790 | } 791 | ``` 792 | 793 | Now this is something you can send for example in Redis or whatever database (abstraction) layer 794 | you have. But, well, this doesn't stop here, if say your database layer is only interested in 795 | `nick`, `email` and `password` (e.g. strip those duplicates), you can even call the `data` table: 796 | 797 | ```lua 798 | local realdata = data("nick", "email", "password") 799 | ``` 800 | 801 | The `realdata` will now contain: 802 | 803 | ```lua 804 | { 805 | nick = "test", 806 | email = "test@test.org", 807 | password = "qwerty123" 808 | } 809 | ``` 810 | 811 | ### field:accept(value) 812 | 813 | For field you can call `accept` that does this: 814 | 815 | ```lua 816 | self.error = nil 817 | self.value = value 818 | self.valid = true 819 | self.invalid = false 820 | self.validated = true 821 | self.unvalidated = false 822 | ``` 823 | 824 | ### field:reject(error) 825 | 826 | For field you can call `reject` that does this: 827 | 828 | ```lua 829 | self.error = error 830 | self.valid = false 831 | self.invalid = true 832 | self.validated = true 833 | self.unvalidated = false 834 | ``` 835 | 836 | ### string field:state(invalid, valid, unvalidated) 837 | 838 | Calling `state` on field is great when embedding validation results inside say HTML template, such as `lua-resty-template`. Here is an example using `lua-resty-template`: 839 | 840 | ```html 841 |
842 | 847 | 848 |
849 | ``` 850 | 851 | So depending on email field's state this will add a class to input element (e.g. making input's border red or green for example). We don't care about unvalidated (e.g. when the user first loaded the page and form) state here. 852 | 853 | ## Changes 854 | 855 | The changes of every release of this module is recorded in [Changes.md](https://github.com/bungle/lua-resty-validation/blob/master/Changes.md) file. 856 | 857 | ## See Also 858 | 859 | * [lua-resty-route](https://github.com/bungle/lua-resty-route) — Routing library 860 | * [lua-resty-reqargs](https://github.com/bungle/lua-resty-reqargs) — Request arguments parser 861 | * [lua-resty-session](https://github.com/bungle/lua-resty-session) — Session library 862 | * [lua-resty-template](https://github.com/bungle/lua-resty-template) — Templating Engine 863 | 864 | ## License 865 | 866 | `lua-resty-validation` uses two clause BSD license. 867 | 868 | ``` 869 | Copyright (c) 2014 - 2017, Aapo Talvensaari 870 | All rights reserved. 871 | 872 | Redistribution and use in source and binary forms, with or without modification, 873 | are permitted provided that the following conditions are met: 874 | 875 | * Redistributions of source code must retain the above copyright notice, this 876 | list of conditions and the following disclaimer. 877 | 878 | * Redistributions in binary form must reproduce the above copyright notice, this 879 | list of conditions and the following disclaimer in the documentation and/or 880 | other materials provided with the distribution. 881 | 882 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 883 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 884 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 885 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 886 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES` 887 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = lua-resty-validation 2 | abstract = Validation Library (Input Validation and Filtering) for Lua and OpenResty 3 | author = Aapo Talvensaari (@bungle) 4 | is_original = yes 5 | license = 2bsd 6 | repo_link = https://github.com/bungle/lua-resty-validation 7 | -------------------------------------------------------------------------------- /lib/resty/validation.lua: -------------------------------------------------------------------------------- 1 | local _VERSION = "2.7" 2 | local setmetatable = setmetatable 3 | local getmetatable = getmetatable 4 | local rawget = rawget 5 | local select = select 6 | local tostring = tostring 7 | local tonumber = tonumber 8 | local type = type 9 | local pairs = pairs 10 | local ipairs = ipairs 11 | local error = error 12 | local pcall = pcall 13 | local string = string 14 | local match = string.match 15 | local lower = string.lower 16 | local upper = string.upper 17 | local find = string.find 18 | local gsub = string.gsub 19 | local sub = string.sub 20 | local len = utf8 and utf8.len or function(s) return select(2, gsub(s, '[^\x80-\xC1]', '')) end 21 | local iotype = io.type 22 | local math = math 23 | local mathtype = math.type 24 | local tointeger = math.tointeger 25 | local abs = math.abs 26 | local unpack = unpack or table.unpack 27 | local nothing = {} 28 | local inf = math.huge 29 | local sreverse = string.reverse 30 | local stopped = {} 31 | local operators = { "<=", ">=", "==", "~=", "<", ">" } 32 | local function stop(value) 33 | return setmetatable({ value = value }, stopped) 34 | end 35 | local function reverse(s) 36 | return sreverse(gsub(s, "[%z-\x7F\xC2-\xF4][\x80-\xBF]*", function(c) return #c > 1 and sreverse(c) end)) 37 | end 38 | local function trim(s) 39 | return (gsub(s, "%s+$", ""):gsub("^%s+", "")) 40 | end 41 | if not mathtype then 42 | mathtype = function(value) 43 | if type(value) ~= "number" then 44 | return nil 45 | end 46 | return value % 1 == 0 and "integer" or "float" 47 | end 48 | end 49 | if not tointeger then 50 | tointeger = function(value) 51 | local v = tonumber(value) 52 | return mathtype(value) == "integer" and v or nil 53 | end 54 | end 55 | local function istype(t) 56 | if t == "integer" or t == "float" then 57 | return function(value) 58 | return t == mathtype(value) 59 | end 60 | elseif t == "file" then 61 | return function(value) 62 | return t == iotype(value) 63 | end 64 | elseif t == "callable" then 65 | return function(value) 66 | if type(value) == "function" then 67 | return true 68 | end 69 | local m = getmetatable(value) 70 | return m and type(m.__call) == "function" 71 | end 72 | else 73 | return function(value) 74 | return t == type(value) 75 | end 76 | end 77 | end 78 | local factory = {} 79 | factory.__index = factory 80 | function factory.type(t) 81 | return istype(t) 82 | end 83 | function factory.iftype(t, truthy, falsy) 84 | local check = istype(t) 85 | return function(value) 86 | if check(value) then 87 | return true, truthy 88 | end 89 | return true, falsy 90 | end 91 | end 92 | function factory.null() 93 | return factory.type("nil") 94 | end 95 | factory["nil"] = factory.null 96 | function factory.boolean() 97 | return factory.type "boolean" 98 | end 99 | function factory.number() 100 | return factory.type "number" 101 | end 102 | function factory.string() 103 | return factory.type "string" 104 | end 105 | function factory.table() 106 | return factory.type "table" 107 | end 108 | function factory.userdata() 109 | return factory.type "userdata" 110 | end 111 | function factory.func() 112 | return factory.type "function" 113 | end 114 | function factory.callable() 115 | return factory.type "callable" 116 | end 117 | factory["function"] = factory.func 118 | function factory.thread() 119 | return factory.type "thread" 120 | end 121 | function factory.integer() 122 | return factory.type "integer" 123 | end 124 | function factory.float() 125 | return factory.type "float" 126 | end 127 | function factory.file() 128 | return factory.type "file" 129 | end 130 | function factory.inf() 131 | return function(value) 132 | return value == inf or value == -inf 133 | end 134 | end 135 | function factory.nan() 136 | return function(value) 137 | return value ~= value 138 | end 139 | end 140 | function factory.finite() 141 | return function(value) 142 | if value ~= value then 143 | return false 144 | end 145 | return value ~= inf and value ~= -inf 146 | end 147 | end 148 | function factory.abs() 149 | return function(value) 150 | return true, abs(value) 151 | end 152 | end 153 | function factory.positive() 154 | return function(value) 155 | return value > 0 156 | end 157 | end 158 | function factory.negative() 159 | return function(value) 160 | return value < 0 161 | end 162 | end 163 | function factory.min(min) 164 | return function(value) 165 | return value >= min 166 | end 167 | end 168 | function factory.max(max) 169 | return function(value) 170 | return value <= max 171 | end 172 | end 173 | function factory.between(min, max) 174 | if not max then max = min end 175 | if max < min then min, max = max, min end 176 | return function(value) 177 | return value >= min and value <= max 178 | end 179 | end 180 | function factory.outside(min, max) 181 | if not max then max = min end 182 | if max < min then min, max = max, min end 183 | return function(value) 184 | return value < min and value > max 185 | end 186 | end 187 | function factory.divisible(number) 188 | return function(value) 189 | if type(value) == "number" then 190 | return value % number == 0 191 | else 192 | return false 193 | end 194 | end 195 | end 196 | function factory.indivisible(number) 197 | return function(value) 198 | if type(value) == "number" then 199 | return value % number ~= 0 200 | else 201 | return false 202 | end 203 | end 204 | end 205 | function factory.len(min, max) 206 | if max then 207 | if max < min then min, max = max, min end 208 | else 209 | max = min 210 | end 211 | return function(value) 212 | local t = type(value) 213 | if t ~= "string" and t ~= "table" then return false end 214 | if type(min) ~= "number" or type(max) ~= "number" or type(value) == "nil" then return false end 215 | local l 216 | if t == "string" then l = len(value) else l = #value end 217 | if type(l) ~= "number" then return false end 218 | return l >= min and l <= max 219 | end 220 | end 221 | function factory.minlen(min) 222 | return function(value) 223 | local t = type(value) 224 | if t ~= "string" and t ~= "table" then return false end 225 | if type(min) ~= "number" or type(value) == "nil" then return false end 226 | local l 227 | if t == "string" then l = len(value) else l = #value end 228 | if type(l) ~= "number" then return false end 229 | return l >= min 230 | end 231 | end 232 | function factory.maxlen(max) 233 | return function(value) 234 | local t = type(value) 235 | if t ~= "string" and t ~= "table" then return false end 236 | if type(max) ~= "number" then return false end 237 | local l 238 | if t == "string" then l = len(value) else l = #value end 239 | if type(l) ~= "number" then return false end 240 | return l <= max 241 | end 242 | end 243 | function factory.equals(equal) 244 | return function(value) 245 | return value == equal 246 | end 247 | end 248 | factory.equal = factory.equals 249 | function factory.unequals(unequal) 250 | return function(value) 251 | return value ~= unequal 252 | end 253 | end 254 | factory.unequal = factory.unequals 255 | function factory.oneof(...) 256 | local n = select("#", ...) 257 | local args = { ... } 258 | return function(value) 259 | for i = 1, n do 260 | if value == args[i] then 261 | return true 262 | end 263 | end 264 | return false 265 | end 266 | end 267 | function factory.noneof(...) 268 | local n = select("#", ...) 269 | local args = { ... } 270 | return function(value) 271 | for i = 1, n do 272 | if value == args[i] then 273 | return false 274 | end 275 | end 276 | return true 277 | end 278 | end 279 | function factory.match(pattern, init) 280 | return function(value) 281 | return match(value, pattern, init) ~= nil 282 | end 283 | end 284 | function factory.unmatch(pattern, init) 285 | return function(value) 286 | return match(value, pattern, init) == nil 287 | end 288 | end 289 | function factory.tostring() 290 | return function(value) 291 | return true, tostring(value) 292 | end 293 | end 294 | function factory.tonumber(base) 295 | return function(value) 296 | local nbr = tonumber(value, base) 297 | return nbr ~= nil, nbr 298 | end 299 | end 300 | function factory.tointeger() 301 | return function(value) 302 | local nbr = tointeger(value) 303 | return nbr ~= nil, nbr 304 | end 305 | end 306 | function factory.toboolean() 307 | return function(value) 308 | return true, not not value 309 | end 310 | end 311 | function factory.tonil() 312 | return function() 313 | return true, nothing 314 | end 315 | end 316 | factory.tonull = factory.tonil 317 | function factory.lower() 318 | return function(value) 319 | local t = type(value) 320 | if t == "string" or t == "number" then 321 | return true, lower(value) 322 | end 323 | return false 324 | end 325 | end 326 | function factory.upper() 327 | return function(value) 328 | local t = type(value) 329 | if t == "string" or t == "number" then 330 | return true, upper(value) 331 | end 332 | return false 333 | end 334 | end 335 | function factory.trim(pattern) 336 | pattern = pattern or "%s+" 337 | local l = "^" .. pattern 338 | local r = pattern .. "$" 339 | return function(value) 340 | local t = type(value) 341 | if t == "string" or t == "number" then 342 | return true, (gsub(value, r, ""):gsub(l, "")) 343 | end 344 | return false 345 | end 346 | end 347 | function factory.ltrim(pattern) 348 | pattern = "^" .. (pattern or "%s+") 349 | return function(value) 350 | local t = type(value) 351 | if t == "string" or t == "number" then 352 | return true, (gsub(value, pattern, "")) 353 | end 354 | return false 355 | end 356 | end 357 | function factory.rtrim(pattern) 358 | pattern = (pattern or "%s+") .. "$" 359 | return function(value) 360 | local t = type(value) 361 | if t == "string" or t == "number" then 362 | return true, (gsub(value, pattern, "")) 363 | end 364 | return false 365 | end 366 | end 367 | function factory.starts(starts) 368 | return function(value) 369 | return sub(value, 1, len(starts)) == starts 370 | end 371 | end 372 | function factory.ends(ends) 373 | return function(value) 374 | return ends == '' or sub(value, -len(ends)) == ends 375 | end 376 | end 377 | function factory.reverse() 378 | return function(value) 379 | local t = type(value) 380 | if t == "string" or t == "number" then 381 | return true, reverse(value) 382 | end 383 | return false 384 | end 385 | end 386 | function factory.coalesce(...) 387 | local args = { ... } 388 | return function(value) 389 | if value ~= nil then return true, value end 390 | for _, v in ipairs(args) do 391 | if v ~= nil then return true, v end 392 | end 393 | return true 394 | end 395 | end 396 | function factory.email() 397 | return function(value) 398 | if value == nil or type(value) ~= "string" then return false end 399 | local i, at = find(value, "@", 1, true), nil 400 | while i do 401 | at = i 402 | i = find(value, "@", i + 1) 403 | end 404 | if not at or at > 65 or at == 1 then return false end 405 | local lp = sub(value, 1, at - 1) 406 | if not lp then return false end 407 | local dp = sub(value, at + 1) 408 | if not dp or #dp > 254 then return false end 409 | local qp = find(lp, '"', 1, true) 410 | if qp and qp > 1 then return false end 411 | local q, p 412 | for i = 1, #lp do 413 | local c = sub(lp, i, i) 414 | if c == "@" then 415 | if not q then return false end 416 | elseif c == '"' then 417 | if p ~= [[\]] then 418 | q = not q 419 | end 420 | elseif c == " " or c == '"' or c == [[\]] then 421 | if not q then 422 | return false 423 | end 424 | end 425 | p = c 426 | end 427 | if q or find(lp, "..", 1, true) or find(dp, "..", 1, true) then return false end 428 | if match(lp, "^%s+") or match(dp, "%s+$") then return false end 429 | return match(value, "%w*%p*@+%w*%.?%w*") ~= nil 430 | end 431 | end 432 | function factory.call(func) 433 | return function(value) 434 | return func(value) 435 | end 436 | end 437 | function factory.optional(default) 438 | return function(value) 439 | if value == nil or value == "" then 440 | return stop, default ~= nil and default or value 441 | end 442 | return true, value 443 | end 444 | end 445 | local validators = setmetatable({ 446 | ["nil"] = factory.null(), 447 | null = factory.null(), 448 | boolean = factory.boolean(), 449 | number = factory.number(), 450 | string = factory.string(), 451 | table = factory.table(), 452 | userdata = factory.userdata(), 453 | ["function"] = factory.func(), 454 | func = factory.func(), 455 | callable = factory.callable(), 456 | thread = factory.thread(), 457 | integer = factory.integer(), 458 | float = factory.float(), 459 | file = factory.file(), 460 | tostring = factory.tostring(), 461 | tonumber = factory.tonumber(), 462 | tointeger = factory.tointeger(), 463 | toboolean = factory.toboolean(), 464 | tonil = factory.tonil(), 465 | tonull = factory.tonull(), 466 | abs = factory.abs(), 467 | inf = factory.inf(), 468 | nan = factory.nan(), 469 | finite = factory.finite(), 470 | positive = factory.positive(), 471 | negative = factory.negative(), 472 | lower = factory.lower(), 473 | upper = factory.upper(), 474 | trim = factory.trim(), 475 | ltrim = factory.ltrim(), 476 | rtrim = factory.rtrim(), 477 | reverse = factory.reverse(), 478 | email = factory.email(), 479 | optional = factory.optional() 480 | }, factory) 481 | local data = {} 482 | function data:__call(...) 483 | local argc = select("#", ...) 484 | local data = setmetatable({}, data) 485 | if argc == 0 then 486 | return self 487 | else 488 | for _, index in ipairs{ ... } do 489 | if self[index] then 490 | data[index] = self[index] 491 | end 492 | end 493 | end 494 | return data 495 | end 496 | local field = {} 497 | field.__index = field 498 | function field.new(name, input) 499 | return setmetatable({ 500 | name = name, 501 | input = input, 502 | value = input, 503 | valid = true, 504 | invalid = false, 505 | validated = false, 506 | unvalidated = true 507 | }, field) 508 | end 509 | function field:__tostring() 510 | if type(self.value) == "string" then return self.value end 511 | return tostring(self.value) 512 | end 513 | function field:state(invalid, valid, unvalidated) 514 | if self.unvalidated then 515 | return unvalidated 516 | end 517 | return self.valid and valid or invalid 518 | end 519 | function field:accept(value) 520 | self.error = nil 521 | self.value = value 522 | self.valid = true 523 | self.invalid = false 524 | self.validated = true 525 | self.unvalidated = false 526 | end 527 | function field:reject(error) 528 | self.error = error 529 | self.valid = false 530 | self.invalid = true 531 | self.validated = true 532 | self.unvalidated = false 533 | end 534 | local fields = {} 535 | function fields:__call(...) 536 | local valid, invalid, validated, unvalidated 537 | local argc = select("#", ...) 538 | if argc == 0 then 539 | valid = true 540 | else 541 | for _, v in ipairs({ ... }) do 542 | if v == "valid" then 543 | valid = true 544 | elseif v == "invalid" then 545 | invalid = true 546 | elseif v == "validated" then 547 | validated = true 548 | elseif v == "unvalidated" then 549 | unvalidated = true 550 | elseif v == "all" then 551 | valid = true 552 | invalid = true 553 | validated = true 554 | unvalidated = true 555 | end 556 | end 557 | end 558 | local data = setmetatable({}, data) 559 | for index, field in pairs(self) do 560 | if valid and field.valid then 561 | data[index] = field.value 562 | elseif invalid and field.invalid then 563 | data[index] = field.value 564 | elseif validated and field.validated then 565 | data[index] = field.value 566 | elseif unvalidated and field.unvalidated then 567 | data[index] = field.value 568 | end 569 | end 570 | return data 571 | end 572 | function fields:__index() 573 | return field.new() 574 | end 575 | local group = {} 576 | group.__index = group 577 | function group:compare(comparison) 578 | local s, e, o 579 | for _, operator in ipairs(operators) do 580 | s, e = find(comparison, operator, 2, true) 581 | if s then 582 | o = operator 583 | break 584 | end 585 | end 586 | local f1 = trim(sub(comparison, 1, s - 1)) 587 | local f2 = trim(sub(comparison, e + 1)) 588 | self[#self+1] = function(fields) 589 | if not fields[f1] then 590 | fields[f1] = field.new(f1) 591 | end 592 | if not fields[f2] then 593 | fields[f2] = field.new(f2) 594 | end 595 | local v1 = fields[f1] 596 | local v2 = fields[f2] 597 | if v1.valid and v2.valid then 598 | local valid, x, y = true, v1.value, v2.value 599 | if o == "<=" then 600 | valid = x <= y 601 | elseif o == ">=" then 602 | valid = x >= y 603 | elseif o == "==" then 604 | valid = x == y 605 | elseif o == "~=" then 606 | valid = x ~= y 607 | elseif o == "<" then 608 | valid = x < y 609 | elseif o == ">" then 610 | valid = x > y 611 | end 612 | if valid then 613 | v1:accept(x) 614 | v2:accept(y) 615 | else 616 | v1:reject "compare" 617 | v2:reject "compare" 618 | end 619 | end 620 | end 621 | end 622 | function group:requisite(r) 623 | local c = #r 624 | self[#self+1] = function(fields) 625 | local n = c 626 | local valid = true 627 | for i = 1, c do 628 | local f = r[i] 629 | if not fields[f] then 630 | fields[f] = field.new(f) 631 | end 632 | local field = fields[f] 633 | if field.valid then 634 | local v = field.value 635 | if v == nil or v == "" then 636 | n = n - 1 637 | end 638 | end 639 | end 640 | if n > 0 then 641 | for i = 1, c do 642 | local f = fields[r[i]] 643 | if f.valid then 644 | f:accept(f.value) 645 | end 646 | end 647 | else 648 | for i = 1, c do 649 | local f = fields[r[i]] 650 | f:reject "requisite" 651 | end 652 | end 653 | end 654 | end 655 | function group:requisites(r, n) 656 | local c = #r 657 | local n = n or c 658 | self[#self+1] = function(fields) 659 | local j = c 660 | local valid = true 661 | for i = 1, c do 662 | local f = r[i] 663 | if not fields[f] then 664 | fields[f] = field.new(f) 665 | end 666 | local field = fields[f] 667 | if field.valid then 668 | local v = field.value 669 | if v == nil or v == "" then 670 | j = j - 1 671 | end 672 | end 673 | end 674 | if n <= j then 675 | for i = 1, c do 676 | local f = fields[r[i]] 677 | if f.valid then 678 | f:accept(f.value) 679 | end 680 | end 681 | else 682 | for i = 1, c do 683 | local f = fields[r[i]] 684 | f:reject "requisites" 685 | end 686 | end 687 | end 688 | end 689 | function group:call(func) 690 | self[#self+1] = func 691 | end 692 | function group:__call(data) 693 | local results = setmetatable({}, fields) 694 | local validators = self.validators 695 | for name, func in pairs(validators) do 696 | local input = data[name] 697 | local valid, value = func(input) 698 | local fld = field.new(name, input) 699 | if valid then 700 | fld:accept(value) 701 | else 702 | fld:reject(value) 703 | end 704 | results[name] = fld 705 | end 706 | for name, input in pairs(data) do 707 | if not results[name] then 708 | results[name] = field.new(name, input) 709 | end 710 | end 711 | for _, v in ipairs(self) do 712 | v(results) 713 | end 714 | local errors 715 | for name, field in pairs(results) do 716 | if field.invalid then 717 | if not errors then 718 | errors = {} 719 | end 720 | errors[name] = field.error or "unknown" 721 | end 722 | end 723 | return errors == nil, results, errors 724 | end 725 | local function new(validators) 726 | return setmetatable({ validators = validators }, group) 727 | end 728 | local function check(validator, value, valid, v) 729 | if not valid then 730 | error(validator, 0) 731 | elseif getmetatable(valid) == stopped then 732 | error(valid, 0) 733 | elseif v == stop then 734 | error(stop(value), 0) 735 | elseif getmetatable(v) == stopped then 736 | error(v, 0) 737 | elseif v == nothing then 738 | v = nil 739 | elseif v == nil then 740 | v = value 741 | end 742 | if valid == stop then 743 | error(stop(v), 0) 744 | end 745 | return true, v 746 | end 747 | local function validation(func, parent_f, parent, method) 748 | return setmetatable({ new = new, group = group, fields = setmetatable({}, fields), nothing = nothing, stop = stop, validators = validators, _VERSION = _VERSION }, { 749 | __index = function(self, index) 750 | return validation(function(...) 751 | local valid, value = check(index, select(1, ...), func(...)) 752 | local validator = rawget(validators, index) 753 | if not validator then 754 | error(index, 0) 755 | end 756 | return check(index, value, validator(value)) 757 | end, func, self, index) 758 | end, 759 | __call = function(_, self, ...) 760 | if parent ~= nil and self == parent then 761 | local n = select("#", ...) 762 | local args = { ... } 763 | return validation(function(...) 764 | local valid, value = check(method, select(1, ...), parent_f(...)) 765 | if sub(method, 1, 2) == "if" then 766 | local validator = rawget(getmetatable(validators), sub(method, 3)) 767 | if not validator then error(method, 0) end 768 | local v 769 | if n > 2 then 770 | valid, v = validator(unpack(args, 1, n - 2))(value) 771 | else 772 | valid, v = validator()(value) 773 | end 774 | return check(method, value, true, valid and args[n - 1] or args[n]) 775 | end 776 | local validator = rawget(getmetatable(validators), method) 777 | if not validator then error(method, 0) end 778 | return check(method, value, validator(unpack(args, 1, n))(value)) 779 | end) 780 | end 781 | local ok, error, value = pcall(func, self, ...) 782 | if ok then 783 | return true, value 784 | elseif getmetatable(error) == stopped then 785 | return true, error.value 786 | end 787 | return false, error 788 | end 789 | }) 790 | end 791 | return validation(function(...) 792 | return true, ... 793 | end) 794 | -------------------------------------------------------------------------------- /lib/resty/validation/injection.lua: -------------------------------------------------------------------------------- 1 | local i = require "resty.injection" 2 | local validation = require "resty.validation" 3 | local validators = validation.validators 4 | function validators.sqli(value) 5 | return not i.sql(value) 6 | end 7 | function validators.xss(value) 8 | return not i.xss(value) 9 | end 10 | return { 11 | sqli = validators.sqli, 12 | xss = validators.xss 13 | } -------------------------------------------------------------------------------- /lib/resty/validation/ngx.lua: -------------------------------------------------------------------------------- 1 | local validation = require "resty.validation" 2 | local ngx = ngx 3 | local escapeuri = ngx.escape_uri 4 | local unescapeuri = ngx.unescape_uri 5 | local base64enc = ngx.encode_base64 6 | local base64dec = ngx.decode_base64 7 | local crc32short = ngx.crc32_short 8 | local crc32long = ngx.crc32_long 9 | local md5 = ngx.md5 10 | local md5bin = ngx.md5bin 11 | local match = ngx.re.match 12 | local validators = validation.validators 13 | local factory = getmetatable(validators) 14 | function factory.escapeuri() 15 | return function(value) 16 | return true, escapeuri(value) 17 | end 18 | end 19 | function factory.unescapeuri() 20 | return function(value) 21 | return true, unescapeuri(value) 22 | end 23 | end 24 | function factory.base64enc() 25 | return function(value) 26 | return true, base64enc(value) 27 | end 28 | end 29 | function factory.base64dec() 30 | return function(value) 31 | local decoded = base64dec(value) 32 | if decoded == nil then 33 | return false 34 | end 35 | return true, decoded 36 | end 37 | end 38 | function factory.crc32short() 39 | return function(value) 40 | return true, crc32short(value) 41 | end 42 | end 43 | function factory.crc32long() 44 | return function(value) 45 | return true, crc32long(value) 46 | end 47 | end 48 | function factory.crc32() 49 | return function(value) 50 | if #value < 61 then 51 | return true, crc32short(value) 52 | end 53 | return true, crc32long(value) 54 | end 55 | end 56 | function factory.md5(bin) 57 | return function(value) 58 | local digest = bin and md5bin(value) or md5(value) 59 | return true, digest 60 | end 61 | end 62 | 63 | function factory.regex(regex, options) 64 | return function(value) 65 | return (match(value, regex, options)) ~= nil 66 | end 67 | end 68 | validators.escapeuri = factory.escapeuri() 69 | validators.unescapeuri = factory.unescapeuri() 70 | validators.base64enc = factory.base64enc() 71 | validators.base64dec = factory.base64dec() 72 | validators.crc32short = factory.crc32short() 73 | validators.crc32long = factory.crc32long() 74 | validators.crc32 = factory.crc32() 75 | validators.md5 = factory.md5() 76 | return { 77 | escapeuri = validators.escapeuri, 78 | unescapeuri = validators.unescapeuri, 79 | base64enc = validators.base64enc, 80 | base64dec = validators.base64dec, 81 | crc32short = validators.crc32short, 82 | crc32long = validators.crc32long, 83 | crc32 = validators.crc32, 84 | md5 = validators.md5, 85 | regex = factory.regex 86 | } -------------------------------------------------------------------------------- /lib/resty/validation/tz.lua: -------------------------------------------------------------------------------- 1 | local parse = require "luatz.parse".rfc_3339 2 | local validation = require "resty.validation" 3 | local validators = validation.validators 4 | local factory = getmetatable(validators) 5 | function factory.totimetable() 6 | return function(value) 7 | if #value == 10 then 8 | value = value .. "T00:00:00Z" 9 | end 10 | local tt = parse(value) 11 | if tt then 12 | return true, tt 13 | end 14 | return false 15 | end 16 | end 17 | function factory.totimestamp() 18 | return function(value) 19 | local ok, tt = validators.totimetable(value) 20 | if ok then 21 | return true, tt:timestamp() 22 | end 23 | return false 24 | end 25 | end 26 | validators.totimetable = factory.totimetable() 27 | validators.totimestamp = factory.totimestamp() 28 | return { 29 | totimetable = validators.totimetable, 30 | totimestamp = validators.totimestamp 31 | } -------------------------------------------------------------------------------- /lib/resty/validation/utf8.lua: -------------------------------------------------------------------------------- 1 | local u = require "resty.utf8rewind" 2 | local validation = require "resty.validation" 3 | local validators = validation.validators 4 | local factory = getmetatable(validators) 5 | local type = type 6 | function factory.utf8upper() 7 | return function(value) 8 | local t = type(value) 9 | if t == "string" then 10 | return true, u.utf8toupper(value) 11 | end 12 | return false 13 | end 14 | end 15 | function factory.utf8lower() 16 | return function(value) 17 | local t = type(value) 18 | if t == "string" then 19 | return true, u.utf8tolower(value) 20 | end 21 | return false 22 | end 23 | end 24 | function factory.utf8title() 25 | return function(value) 26 | local t = type(value) 27 | if t == "string" then 28 | return true, u.utf8title(value) 29 | end 30 | return false 31 | end 32 | end 33 | function factory.utf8normalize(form) 34 | return function(value) 35 | local t = type(value) 36 | if t == "string" then 37 | return true, u.utf8normalize(value, form) 38 | end 39 | return false 40 | end 41 | end 42 | function factory.utf8category(category) 43 | return function(value) 44 | local t = type(value) 45 | if t == "string" then 46 | return (u.utf8iscategory(value, category)) 47 | end 48 | return false 49 | end 50 | end 51 | validators.utf8upper = factory.utf8upper() 52 | validators.utf8lower = factory.utf8lower() 53 | validators.utf8title = factory.utf8title() 54 | return { 55 | utf8upper = validators.utf8upper, 56 | utf8lower = validators.utf8lower, 57 | utf8title = validators.utf8title, 58 | utf8normalize = factory.utf8normalize, 59 | utf8category = factory.utf8category 60 | } -------------------------------------------------------------------------------- /lua-resty-validation-dev-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-validation" 2 | version = "dev-1" 3 | source = { 4 | url = "git://github.com/bungle/lua-resty-validation.git" 5 | } 6 | description = { 7 | summary = "Validation Library (Input Validation and Filtering) for Lua and OpenResty", 8 | detailed = "lua-resty-validation is an extendable chaining validation and filtering library for Lua and OpenResty.", 9 | homepage = "https://github.com/bungle/lua-resty-validation", 10 | maintainer = "Aapo Talvensaari ", 11 | license = "BSD" 12 | } 13 | dependencies = { 14 | "lua >= 5.1" 15 | } 16 | build = { 17 | type = "builtin", 18 | modules = { 19 | ["resty.validation"] = "lib/resty/validation.lua", 20 | ["resty.validation.tz"] = "lib/resty/validation/tz.lua", 21 | ["resty.validation.ngx"] = "lib/resty/validation/ngx.lua", 22 | ["resty.validation.injection"] = "lib/resty/validation/injection.lua" 23 | } 24 | } 25 | --------------------------------------------------------------------------------