A validator for user parameters.
require 'strelka/paramvalidator' validator = Strelka::ParamValidator.new # Add validation criteria for input parameters validator.add( :name, /^(?<lastname>\S+), (?<firstname>\S+)$/, "Customer Name" ) validator.add( :email, "Customer Email" ) validator.add( :feedback, :printable, "Customer Feedback" ) validator.override( :email, :printable, "Your Email Address" ) # Untaint all parameter values which match their constraints validate.untaint_all_constraints = true # Now pass in tainted values in a hash (e.g., from an HTML form) validator.validate( req.params ) # Now if there weren't any errors, use some form values to fill out the # success page template if validator.okay? tmpl = template :success tmpl.firstname = validator[:name][:firstname] tmpl.lastname = validator[:name][:lastname] tmpl.email = validator[:email] tmpl.feedback = validator[:feedback] return tmpl # Otherwise fill in the error template with auto-generated error messages # and return that instead. else tmpl = template :feedback_form tmpl.errors = validator.error_messages return tmpl end
Pattern to use to strip binding operators from parameter patterns so they can be used in the middle of routing Regexps.
Pattern for countint the number of hash levels in a parameter key
The constraints hash
The Hash of raw field data (if validation has occurred)
Create a new Strelka::ParamValidator object.
# File lib/strelka/paramvalidator.rb, line 565
def initialize
@constraints = {}
@fields = {}
@untaint_all = false
self.reset
end
Index fetch operator; fetch the validated (and possible parsed) value for
form field key
.
# File lib/strelka/paramvalidator.rb, line 831
def []( key )
self.validate unless self.validated?
return @valid[ key.to_sym ]
end
Index assignment operator; set the validated value for form field
key
to the specified val
.
# File lib/strelka/paramvalidator.rb, line 839
def []=( key, val )
@parsed_params = nil
@valid[ key.to_sym ] = val
end
Add a validation for a parameter with the specified name
. The
args
can include a constraint, a description, and one or more
flags.
# File lib/strelka/paramvalidator.rb, line 625
def add( name, *args, &block )
name = name.to_sym
constraint = Constraint.for( name, *args, &block )
# No-op if there's already a parameter with the same name and constraint
if self.constraints.key?( name )
return if self.constraints[ name ] == constraint
raise ArgumentError,
"parameter %p is already defined as %s; perhaps you meant to use #override?" %
[ name.to_s, self.constraints[name] ]
end
self.log.debug "Adding parameter %p: %p" % [ name, constraint ]
self.constraints[ name ] = constraint
self.validated = false
end
Apply the specified constraint
(a Strelka::ParamValidator::Constraint
object) to the given value
, and add the field to the
appropriate field list based on the result.
# File lib/strelka/paramvalidator.rb, line 754
def apply_constraint( constraint, value )
if !( value.nil? || value == '' )
result = constraint.apply( value, self.untaint_all? )
if !result.nil?
self.log.debug " constraint for %p passed: %p" % [ constraint.name, result ]
self[ constraint.name ] = result
else
self.log.debug " constraint for %p failed" % [ constraint.name ]
@invalid[ constraint.name.to_s ] = value
end
elsif constraint.required?
self.log.debug " missing parameter for %p" % [ constraint.name ]
@missing << constraint.name.to_s
end
end
Returns true
if there were arguments given.
# File lib/strelka/paramvalidator.rb, line 852
def args?
return !self.fields.empty?
end
Fetch the constraint/s that apply to the parameter named name
as a Regexp, if possible.
# File lib/strelka/paramvalidator.rb, line 782
def constraint_regexp_for( name )
self.log.debug " searching for a constraint for %p" % [ name ]
# Fetch the constraint's regexp
constraint = self.constraints[ name.to_sym ] or
raise NameError, "no such parameter %p" % [ name ]
raise ScriptError,
"can't route on a parameter with a %p" % [ constraint.class ] unless
constraint.respond_to?( :pattern )
re = constraint.pattern
self.log.debug " bounded constraint is: %p" % [ re ]
# Unbind the pattern from beginning or end of line.
# :TODO: This is pretty ugly. Find a better way of modifying the regex.
re_str = re.to_s.
sub( %r{\(\?[\-mix]+:(.*)\)}, '\1' ).
gsub( PARAMETER_PATTERN_STRIP_RE, '' )
self.log.debug " stripped constraint pattern down to: %p" % [ re_str ]
return Regexp.new( "(?<#{name}>#{re_str})", re.options )
end
Hash of field descriptions
# File lib/strelka/paramvalidator.rb, line 694
def descriptions
return self.constraints.each_with_object({}) do |(field,constraint), hash|
hash[ field ] = constraint.description
end
end
Set field descriptions en masse to new_descs.
# File lib/strelka/paramvalidator.rb, line 702
def descriptions=( new_descs )
new_descs.each do |name, description|
raise NameError, "no parameter named #{name}" unless
self.constraints.key?( name.to_sym )
self.constraints[ name.to_sym ].description = description
end
end
Returns true
if there were no arguments given.
# File lib/strelka/paramvalidator.rb, line 846
def empty?
return self.fields.empty?
end
Return an array of field names which had some kind of error associated with them.
# File lib/strelka/paramvalidator.rb, line 896
def error_fields
return self.missing | self.invalid.keys
end
Return an error message for each missing or invalid field; if
includeUnknown
is true
, also include messages for
unknown fields.
# File lib/strelka/paramvalidator.rb, line 903
def error_messages( include_unknown=false )
msgs = []
msgs += self.missing_param_errors + self.invalid_param_errors
msgs += self.unknown_param_errors if include_unknown
return msgs
end
Returns true
if any fields are missing or contain invalid
values.
# File lib/strelka/paramvalidator.rb, line 881
def errors?
return !self.okay?
end
Get the description for the specified field
.
# File lib/strelka/paramvalidator.rb, line 712
def get_description( field )
constraint = self.constraints[ field.to_sym ] or return nil
return constraint.description
end
Copy constructor.
# File lib/strelka/paramvalidator.rb, line 575
def initialize_copy( original )
fields = deep_copy( original.fields )
self.reset
@fields = fields
@constraints = deep_copy( original.constraints )
end
Return a human-readable representation of the validator, suitable for debugging.
# File lib/strelka/paramvalidator.rb, line 677
def inspect
required, optional = self.constraints.partition do |_, constraint|
constraint.required?
end
return "#<%p:0x%016x %s, profile: [required: %s, optional: %s] global untaint: %s>" % [
self.class,
self.object_id / 2,
self.to_s,
required.empty? ? "(none)" : required.map( &:last ).map( &:name ).join(','),
optional.empty? ? "(none)" : optional.map( &:last ).map( &:name ).join(','),
self.untaint_all? ? "enabled" : "disabled",
]
end
The Hash of fields that were present, but invalid (didn't match the field's constraint)
# File lib/strelka/paramvalidator.rb, line 866
def invalid
self.validate unless self.validated?
return @invalid
end
Return an Array of error messages, one for each field that was invalid from the last validation.
# File lib/strelka/paramvalidator.rb, line 925
def invalid_param_errors
return self.invalid.collect do |field, _|
constraint = self.constraints[ field.to_sym ] or
raise NameError, "no such field %p!" % [ field ]
"Invalid value for '%s'" % [ constraint.description ]
end
end
Return a new ParamValidator with the
additional params
merged into its values and re-validated.
# File lib/strelka/paramvalidator.rb, line 946
def merge( params )
copy = self.dup
copy.merge!( params )
return copy
end
Merge the specified params
into the receiving ParamValidator and re-validate the resulting
values.
# File lib/strelka/paramvalidator.rb, line 955
def merge!( params )
return if params.empty?
self.log.debug "Merging parameters for revalidation: %p" % [ params ]
self.revalidate( params )
end
The names of fields that were required, but missing from the parameter list.
# File lib/strelka/paramvalidator.rb, line 859
def missing
self.validate unless self.validated?
return @missing
end
Return an Array of error messages, one for each field missing from the last validation.
# File lib/strelka/paramvalidator.rb, line 914
def missing_param_errors
return self.missing.collect do |field|
constraint = self.constraints[ field.to_sym ] or
raise NameError, "no such field %p!" % [ field ]
"Missing value for '%s'" % [ constraint.description ]
end
end
Return true
if all required fields were present and all
present fields validated correctly.
# File lib/strelka/paramvalidator.rb, line 889
def okay?
return (self.missing.empty? && self.invalid.empty?)
end
Replace the existing parameter with the specified name. The args replace the existing description, constraints, and flags. See add for details.
# File lib/strelka/paramvalidator.rb, line 646
def override( name, *args, &block )
name = name.to_sym
raise ArgumentError,
"no parameter %p defined; perhaps you meant to use #add?" % [ name.to_s ] unless
self.constraints.key?( name )
self.log.debug "Overriding parameter %p" % [ name ]
self.constraints[ name ] = Constraint.for( name, *args, &block )
self.validated = false
end
Return the Array of parameter names the validator knows how to validate (as Strings).
# File lib/strelka/paramvalidator.rb, line 660
def param_names
return self.constraints.keys.map( &:to_s ).sort
end
Reset the validation state.
# File lib/strelka/paramvalidator.rb, line 606
def reset
self.log.debug "Resetting validation state."
@validated = false
@valid = {}
@parsed_params = nil
@missing = []
@unknown = []
@invalid = {}
end
Clear existing validation information, merge the specified
params
with any existing raw fields, and re-run the
validation.
# File lib/strelka/paramvalidator.rb, line 774
def revalidate( params={} )
merged_fields = self.fields.merge( params )
self.reset
self.validate( merged_fields )
end
Stringified description of the validator
# File lib/strelka/paramvalidator.rb, line 666
def to_s
"%d parameters (%d valid, %d invalid, %d missing)" % [
self.fields.size,
self.valid.size,
self.invalid.size,
self.missing.size,
]
end
The names of fields that were present in the parameters, but didn't have a corresponding constraint.
# File lib/strelka/paramvalidator.rb, line 874
def unknown
self.validate unless self.validated?
return @unknown
end
Return an Array of error messages, one for each field present in the parameters in the last validation that didn't have a constraint associated with it.
# File lib/strelka/paramvalidator.rb, line 936
def unknown_param_errors
self.log.debug "Fetching unknown param errors for %p." % [ self.unknown ]
return self.unknown.collect do |field|
"Unknown parameter '%s'" % [ field.capitalize ]
end
end
Global untainting flag
# File lib/strelka/paramvalidator.rb, line 595
attr_predicate_accessor :untaint_all?
Returns the valid fields after expanding Rails-style 'customer[street]' variables into multi-level hashes.
# File lib/strelka/paramvalidator.rb, line 808
def valid
self.validate unless self.validated?
self.log.debug "Building valid fields hash from raw data: %p" % [ @valid ]
unless @parsed_params
@parsed_params = {}
for key, value in @valid
self.log.debug " adding %s: %p" % [ key, value ]
value = [ value ] if key.to_s.end_with?( '[]' )
if key.to_s.include?( '[' )
build_deep_hash( value, @parsed_params, get_levels(key.to_s) )
else
@parsed_params[ key ] = value
end
end
end
return @parsed_params
end
Validate the input in params
. If the optional
additional_constraints
is given, merge it with the
validator's existing constraints before validating.
# File lib/strelka/paramvalidator.rb, line 720
def validate( params=nil, additional_constraints=nil )
self.log.debug "Validating."
self.reset
# :TODO: Handle the additional_constraints
params ||= @fields
params = stringify_keys( params )
@fields = deep_copy( params )
self.log.debug "Starting validation with fields: %p" % [ @fields ]
# Use the constraints list to extract all the parameters that have corresponding
# constraints
self.constraints.each do |field, constraint|
self.log.debug " applying %s to any %p parameter/s" % [ constraint, field ]
value = params.delete( field.to_s )
self.log.debug " value is: %p" % [ value ]
self.apply_constraint( constraint, value )
end
# Any left over are unknown
params.keys.each do |field|
self.log.debug " unknown field %p" % [ field ]
@unknown << field
end
@validated = true
end
Returns true
if the paramvalidator has been given parameters
to validate. Adding or overriding constraints resets this.
# File lib/strelka/paramvalidator.rb, line 602
attr_predicate_accessor :validated?
Returns an array containing valid parameters in the validator corresponding
to the given selector
(s).
# File lib/strelka/paramvalidator.rb, line 964
def values_at( *selector )
selector.map!( &:to_sym )
return self.valid.values_at( *selector )
end