strelka 0.0.1pre4 → 0.0.1.pre129
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +1 -1
- data/IDEAS.rdoc +62 -0
- data/Manifest.txt +38 -7
- data/README.rdoc +124 -5
- data/Rakefile +22 -6
- data/bin/leash +102 -157
- data/contrib/hoetemplate/.autotest.erb +23 -0
- data/contrib/hoetemplate/History.rdoc.erb +4 -0
- data/contrib/hoetemplate/Manifest.txt.erb +8 -0
- data/contrib/hoetemplate/README.rdoc.erb +17 -0
- data/contrib/hoetemplate/Rakefile.erb +24 -0
- data/contrib/hoetemplate/data/file_name/apps/file_name_app +36 -0
- data/contrib/hoetemplate/data/file_name/templates/layout.tmpl.erb +13 -0
- data/contrib/hoetemplate/data/file_name/templates/top.tmpl.erb +8 -0
- data/contrib/hoetemplate/lib/file_name.rb.erb +18 -0
- data/contrib/hoetemplate/spec/file_name_spec.rb.erb +21 -0
- data/data/strelka/apps/hello-world +30 -0
- data/lib/strelka/app/defaultrouter.rb +49 -30
- data/lib/strelka/app/errors.rb +121 -0
- data/lib/strelka/app/exclusiverouter.rb +40 -0
- data/lib/strelka/app/filters.rb +18 -7
- data/lib/strelka/app/negotiation.rb +122 -0
- data/lib/strelka/app/parameters.rb +171 -14
- data/lib/strelka/app/paramvalidator.rb +751 -0
- data/lib/strelka/app/plugins.rb +66 -46
- data/lib/strelka/app/restresources.rb +499 -0
- data/lib/strelka/app/router.rb +73 -0
- data/lib/strelka/app/routing.rb +140 -18
- data/lib/strelka/app/templating.rb +12 -3
- data/lib/strelka/app.rb +174 -24
- data/lib/strelka/constants.rb +0 -20
- data/lib/strelka/exceptions.rb +29 -0
- data/lib/strelka/httprequest/acceptparams.rb +377 -0
- data/lib/strelka/httprequest/negotiation.rb +257 -0
- data/lib/strelka/httprequest.rb +155 -7
- data/lib/strelka/httpresponse/negotiation.rb +579 -0
- data/lib/strelka/httpresponse.rb +140 -0
- data/lib/strelka/logging.rb +4 -1
- data/lib/strelka/mixins.rb +53 -0
- data/lib/strelka.rb +22 -1
- data/spec/data/error.tmpl +1 -0
- data/spec/lib/constants.rb +0 -1
- data/spec/lib/helpers.rb +21 -0
- data/spec/strelka/app/defaultrouter_spec.rb +41 -35
- data/spec/strelka/app/errors_spec.rb +212 -0
- data/spec/strelka/app/exclusiverouter_spec.rb +220 -0
- data/spec/strelka/app/filters_spec.rb +196 -0
- data/spec/strelka/app/negotiation_spec.rb +73 -0
- data/spec/strelka/app/parameters_spec.rb +149 -0
- data/spec/strelka/app/paramvalidator_spec.rb +1059 -0
- data/spec/strelka/app/plugins_spec.rb +26 -19
- data/spec/strelka/app/restresources_spec.rb +393 -0
- data/spec/strelka/app/router_spec.rb +63 -0
- data/spec/strelka/app/routing_spec.rb +183 -9
- data/spec/strelka/app/templating_spec.rb +1 -2
- data/spec/strelka/app_spec.rb +265 -32
- data/spec/strelka/exceptions_spec.rb +53 -0
- data/spec/strelka/httprequest/acceptparams_spec.rb +282 -0
- data/spec/strelka/httprequest/negotiation_spec.rb +246 -0
- data/spec/strelka/httprequest_spec.rb +204 -14
- data/spec/strelka/httpresponse/negotiation_spec.rb +464 -0
- data/spec/strelka/httpresponse_spec.rb +114 -0
- data/spec/strelka/mixins_spec.rb +99 -0
- data.tar.gz.sig +1 -0
- metadata +175 -79
- metadata.gz.sig +2 -0
- data/IDEAS.textile +0 -174
- data/data/strelka/apps/strelka-admin +0 -65
- data/data/strelka/apps/strelka-setup +0 -26
- data/data/strelka/bootstrap-config.rb +0 -34
- data/data/strelka/templates/admin/console.tmpl +0 -21
- data/data/strelka/templates/layout.tmpl +0 -30
- data/lib/strelka/process.rb +0 -19
@@ -0,0 +1,751 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'uri'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'date'
|
7
|
+
require 'formvalidator'
|
8
|
+
|
9
|
+
require 'strelka/mixins'
|
10
|
+
require 'strelka' unless defined?( Strelka )
|
11
|
+
require 'strelka/app' unless defined?( Strelka::App )
|
12
|
+
|
13
|
+
|
14
|
+
# A validator for user parameters.
|
15
|
+
#
|
16
|
+
# == Usage
|
17
|
+
#
|
18
|
+
# require 'strelka/app/formvalidator'
|
19
|
+
#
|
20
|
+
# # Profile specifies validation criteria for input
|
21
|
+
# profile = {
|
22
|
+
# :required => :name,
|
23
|
+
# :optional => [:email, :description],
|
24
|
+
# :filters => [:strip, :squeeze],
|
25
|
+
# :untaint_all_constraints => true,
|
26
|
+
# :descriptions => {
|
27
|
+
# :email => "Customer Email",
|
28
|
+
# :description => "Issue Description",
|
29
|
+
# :name => "Customer Name",
|
30
|
+
# },
|
31
|
+
# :constraints => {
|
32
|
+
# :email => :email,
|
33
|
+
# :name => /^[\x20-\x7f]+$/,
|
34
|
+
# :description => /^[\x20-\x7f]+$/,
|
35
|
+
# },
|
36
|
+
# }
|
37
|
+
#
|
38
|
+
# # Create a validator object and pass in a hash of request parameters and the
|
39
|
+
# # profile hash.
|
40
|
+
# validator = Strelka::App::ParamValidator.new
|
41
|
+
# validator.validate( req_params, profile )
|
42
|
+
#
|
43
|
+
# # Now if there weren't any errors, send the success page
|
44
|
+
# if validator.okay?
|
45
|
+
# return success_template
|
46
|
+
#
|
47
|
+
# # Otherwise fill in the error template with auto-generated error messages
|
48
|
+
# # and return that instead.
|
49
|
+
# else
|
50
|
+
# failure_template.errors( validator.error_messages )
|
51
|
+
# return failure_template
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
class Strelka::App::ParamValidator < ::FormValidator
|
55
|
+
extend Forwardable
|
56
|
+
include Strelka::Loggable
|
57
|
+
|
58
|
+
|
59
|
+
# Validation default config
|
60
|
+
DEFAULT_PROFILE = {
|
61
|
+
:descriptions => {},
|
62
|
+
}
|
63
|
+
|
64
|
+
#
|
65
|
+
# RFC822 Email Address Regex
|
66
|
+
# --------------------------
|
67
|
+
#
|
68
|
+
# Originally written by Cal Henderson
|
69
|
+
# c.f. http://iamcal.com/publish/articles/php/parsing_email/
|
70
|
+
#
|
71
|
+
# Translated to Ruby by Tim Fletcher, with changes suggested by Dan Kubb.
|
72
|
+
#
|
73
|
+
# Licensed under a Creative Commons Attribution-ShareAlike 2.5 License
|
74
|
+
# http://creativecommons.org/licenses/by-sa/2.5/
|
75
|
+
#
|
76
|
+
RFC822_EMAIL_ADDRESS = begin
|
77
|
+
qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]'
|
78
|
+
dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]'
|
79
|
+
atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-' +
|
80
|
+
'\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+'
|
81
|
+
quoted_pair = '\\x5c[\\x00-\\x7f]'
|
82
|
+
domain_literal = "\\x5b(?:#{dtext}|#{quoted_pair})*\\x5d"
|
83
|
+
quoted_string = "\\x22(?:#{qtext}|#{quoted_pair})*\\x22"
|
84
|
+
domain_ref = atom
|
85
|
+
sub_domain = "(?:#{domain_ref}|#{domain_literal})"
|
86
|
+
word = "(?:#{atom}|#{quoted_string})"
|
87
|
+
domain = "#{sub_domain}(?:\\x2e#{sub_domain})*"
|
88
|
+
local_part = "#{word}(?:\\x2e#{word})*"
|
89
|
+
addr_spec = "#{local_part}\\x40#{domain}"
|
90
|
+
/\A#{addr_spec}\z/n
|
91
|
+
end
|
92
|
+
|
93
|
+
# Pattern for (loosely) matching a valid hostname. This isn't strictly RFC-compliant
|
94
|
+
# because, in practice, many hostnames used on the Internet aren't.
|
95
|
+
RFC1738_HOSTNAME = begin
|
96
|
+
alphadigit = /[a-z0-9]/i
|
97
|
+
# toplabel = alpha | alpha *[ alphadigit | "-" ] alphadigit
|
98
|
+
toplabel = /[a-z]((#{alphadigit}|-)*#{alphadigit})?/i
|
99
|
+
# domainlabel = alphadigit | alphadigit *[ alphadigit | "-" ] alphadigit
|
100
|
+
domainlabel = /#{alphadigit}((#{alphadigit}|-)*#{alphadigit})?/i
|
101
|
+
# hostname = *[ domainlabel "." ] toplabel
|
102
|
+
hostname = /\A(#{domainlabel}\.)*#{toplabel}\z/
|
103
|
+
end
|
104
|
+
|
105
|
+
# Pattern for countint the number of hash levels in a parameter key
|
106
|
+
PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/
|
107
|
+
|
108
|
+
# The Hash of builtin constraints that are validated against a regular
|
109
|
+
# expression.
|
110
|
+
BUILTIN_CONSTRAINT_PATTERNS = {
|
111
|
+
:boolean => /^(?<boolean>t(?:rue)?|y(?:es)?|[10]|no?|f(?:alse)?)$/i,
|
112
|
+
:integer => /^(?<integer>[\-\+]?\d+)$/,
|
113
|
+
:float => /^(?<float>[\-\+]?(?:\d*\.\d+|\d+)(?:e[\-\+]?\d+)?)$/i,
|
114
|
+
:alpha => /^(?<alpha>[[:alpha:]]+)$/,
|
115
|
+
:alphanumeric => /^(?<alphanumeric>[[:alnum:]]+)$/,
|
116
|
+
:printable => /\A(?<printable>[[:print:][:blank:]\r\n]+)\z/,
|
117
|
+
:string => /\A(?<string>[[:print:][:blank:]\r\n]+)\z/,
|
118
|
+
:word => /^(?<word>[[:word:]]+)$/,
|
119
|
+
:email => /^(?<email>#{RFC822_EMAIL_ADDRESS})$/,
|
120
|
+
:hostname => /^(?<hostname>#{RFC1738_HOSTNAME})$/,
|
121
|
+
:uri => /^(?<uri>#{URI::URI_REF})$/,
|
122
|
+
}
|
123
|
+
|
124
|
+
|
125
|
+
### Return a Regex for the built-in constraint associated with the given +name+. If
|
126
|
+
### the builtin constraint is not pattern-based, or there is no such constraint,
|
127
|
+
### returns +nil+.
|
128
|
+
def self::pattern_for_constraint( name )
|
129
|
+
return BUILTIN_CONSTRAINT_PATTERNS[ name.to_sym ]
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
#################################################################
|
134
|
+
### I N S T A N C E M E T H O D S
|
135
|
+
#################################################################
|
136
|
+
|
137
|
+
### Create a new Strelka::App::ParamValidator object.
|
138
|
+
def initialize( profile, params=nil )
|
139
|
+
@form = {}
|
140
|
+
@raw_form = {}
|
141
|
+
@profile = DEFAULT_PROFILE.merge( profile )
|
142
|
+
@invalid_fields = []
|
143
|
+
@missing_fields = []
|
144
|
+
@unknown_fields = []
|
145
|
+
@required_fields = []
|
146
|
+
@require_some_fields = []
|
147
|
+
@optional_fields = []
|
148
|
+
@filters_array = []
|
149
|
+
@untaint_fields = []
|
150
|
+
|
151
|
+
@parsed_params = nil
|
152
|
+
|
153
|
+
self.validate( params ) if params
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
### Copy constructor.
|
158
|
+
def initialize_copy( original )
|
159
|
+
@form = @form.clone
|
160
|
+
@raw_form = @form.clone
|
161
|
+
@profile = @profile.clone
|
162
|
+
@invalid_fields = @invalid_fields.clone
|
163
|
+
@missing_fields = @missing_fields.clone
|
164
|
+
@unknown_fields = @unknown_fields.clone
|
165
|
+
@required_fields = @required_fields.clone
|
166
|
+
@require_some_fields = @require_some_fields.clone
|
167
|
+
@optional_fields = @optional_fields.clone
|
168
|
+
@filters_array = @filters_array.clone
|
169
|
+
@untaint_fields = @untaint_fields.clone
|
170
|
+
|
171
|
+
@parsed_params = @parsed_params.clone if @parsed_params
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
|
176
|
+
######
|
177
|
+
public
|
178
|
+
######
|
179
|
+
|
180
|
+
# The raw form data Hash
|
181
|
+
attr_reader :raw_form
|
182
|
+
|
183
|
+
# The validated form data Hash
|
184
|
+
attr_reader :form
|
185
|
+
|
186
|
+
|
187
|
+
### Stringified description of the validator
|
188
|
+
def to_s
|
189
|
+
"%d parameters (%d valid, %d invalid, %d missing)" % [
|
190
|
+
self.raw_form.size,
|
191
|
+
self.form.size,
|
192
|
+
self.invalid.size,
|
193
|
+
self.missing.size,
|
194
|
+
]
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
### Hash of field descriptions
|
199
|
+
def descriptions
|
200
|
+
return @profile[:descriptions]
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
### Set hash of field descriptions
|
205
|
+
def descriptions=( new_descs )
|
206
|
+
return @profile[:descriptions] = new_descs
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
### Validate the input in +params+. If the optional +additional_profile+ is
|
211
|
+
### given, merge it with the validator's default profile before validating.
|
212
|
+
def validate( params, additional_profile=nil )
|
213
|
+
@raw_form = params.dup
|
214
|
+
profile = @profile
|
215
|
+
|
216
|
+
if additional_profile
|
217
|
+
self.log.debug "Merging additional profile %p" % [additional_profile]
|
218
|
+
profile = @profile.merge( additional_profile )
|
219
|
+
end
|
220
|
+
|
221
|
+
super( params, profile )
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
### Overridden to remove the check for extra keys.
|
226
|
+
def check_profile_syntax( profile )
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
### Index operator; fetch the validated value for form field +key+.
|
231
|
+
def []( key )
|
232
|
+
return @form[ key.to_s ]
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
### Index assignment operator; set the validated value for form field +key+
|
237
|
+
### to the specified +val+.
|
238
|
+
def []=( key, val )
|
239
|
+
return @form[ key.to_s ] = val
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
### Returns +true+ if there were no arguments given.
|
244
|
+
def empty?
|
245
|
+
return @form.empty?
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
### Returns +true+ if there were arguments given.
|
250
|
+
def args?
|
251
|
+
return !@form.empty?
|
252
|
+
end
|
253
|
+
alias_method :has_args?, :args?
|
254
|
+
|
255
|
+
|
256
|
+
### Returns +true+ if any fields are missing or contain invalid values.
|
257
|
+
def errors?
|
258
|
+
return !self.okay?
|
259
|
+
end
|
260
|
+
alias_method :has_errors?, :errors?
|
261
|
+
|
262
|
+
|
263
|
+
### Return +true+ if all required fields were present and validated
|
264
|
+
### correctly.
|
265
|
+
def okay?
|
266
|
+
return (self.missing.empty? && self.invalid.empty?)
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
### Returns +true+ if the given +field+ is one that should be untainted.
|
271
|
+
def untaint?( field )
|
272
|
+
self.log.debug "Checking to see if %p should be untainted." % [field]
|
273
|
+
rval = ( @untaint_all ||
|
274
|
+
@untaint_fields.include?(field) ||
|
275
|
+
@untaint_fields.include?(field.to_sym) )
|
276
|
+
|
277
|
+
if rval
|
278
|
+
self.log.debug " ...yep it should."
|
279
|
+
else
|
280
|
+
self.log.debug " ...nope; untaint fields is: %p" % [ @untaint_fields ]
|
281
|
+
end
|
282
|
+
|
283
|
+
return rval
|
284
|
+
end
|
285
|
+
|
286
|
+
|
287
|
+
### Overridden so that the presence of :untaint_constraint_fields doesn't
|
288
|
+
### disable the :untaint_all_constraints flag.
|
289
|
+
def untaint_all_constraints
|
290
|
+
@untaint_all = @profile[:untaint_all_constraints] ? true : false
|
291
|
+
end
|
292
|
+
|
293
|
+
|
294
|
+
|
295
|
+
### Return an array of field names which had some kind of error associated
|
296
|
+
### with them.
|
297
|
+
def error_fields
|
298
|
+
return self.missing | self.invalid.keys
|
299
|
+
end
|
300
|
+
|
301
|
+
|
302
|
+
### Get the description for the specified field.
|
303
|
+
def get_description( field )
|
304
|
+
return @profile[:descriptions][ field.to_s ] if
|
305
|
+
@profile[:descriptions].key?( field.to_s )
|
306
|
+
|
307
|
+
desc = field.to_s.
|
308
|
+
gsub( /.*\[(\w+)\]/, "\\1" ).
|
309
|
+
gsub( /_(.)/ ) {|m| " " + m[1,1].upcase }.
|
310
|
+
gsub( /^(.)/ ) {|m| m.upcase }
|
311
|
+
return desc
|
312
|
+
end
|
313
|
+
|
314
|
+
|
315
|
+
### Return an error message for each missing or invalid field; if
|
316
|
+
### +includeUnknown+ is +true+, also include messages for unknown fields.
|
317
|
+
def error_messages( include_unknown=false )
|
318
|
+
self.log.debug "Building error messages from descriptions: %p" %
|
319
|
+
[ @profile[:descriptions] ]
|
320
|
+
msgs = []
|
321
|
+
self.missing.each do |field|
|
322
|
+
msgs << "Missing value for '%s'" % self.get_description( field )
|
323
|
+
end
|
324
|
+
|
325
|
+
self.invalid.each do |field, constraint|
|
326
|
+
msgs << "Invalid value for '%s'" % self.get_description( field )
|
327
|
+
end
|
328
|
+
|
329
|
+
if include_unknown
|
330
|
+
self.unknown.each do |field|
|
331
|
+
msgs << "Unknown parameter '%s'" % self.get_description( field )
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
return msgs
|
336
|
+
end
|
337
|
+
|
338
|
+
|
339
|
+
### Returns a distinct list of missing fields. Overridden to eliminate the
|
340
|
+
### "undefined method `<=>' for :foo:Symbol" error.
|
341
|
+
def missing
|
342
|
+
@missing_fields.uniq.sort_by {|f| f.to_s}
|
343
|
+
end
|
344
|
+
|
345
|
+
### Returns a distinct list of unknown fields.
|
346
|
+
def unknown
|
347
|
+
(@unknown_fields - @invalid_fields.keys).uniq.sort_by {|f| f.to_s}
|
348
|
+
end
|
349
|
+
|
350
|
+
|
351
|
+
### Returns the valid fields after expanding Rails-style
|
352
|
+
### 'customer[address][street]' variables into multi-level hashes.
|
353
|
+
def valid
|
354
|
+
if @parsed_params.nil?
|
355
|
+
@parsed_params = {}
|
356
|
+
valid = super()
|
357
|
+
|
358
|
+
for key, value in valid
|
359
|
+
value = [ value ] if key.end_with?( '[]' )
|
360
|
+
if key.include?( '[' )
|
361
|
+
build_deep_hash( value, @parsed_params, get_levels(key) )
|
362
|
+
else
|
363
|
+
@parsed_params[ key ] = value
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
return @parsed_params
|
369
|
+
end
|
370
|
+
|
371
|
+
|
372
|
+
### Return a new ParamValidator with the additional +params+ merged into
|
373
|
+
### its values and re-validated.
|
374
|
+
def merge( params )
|
375
|
+
copy = self.dup
|
376
|
+
copy.merge!( params )
|
377
|
+
return copy
|
378
|
+
end
|
379
|
+
|
380
|
+
|
381
|
+
### Merge the specified +params+ into the receiving ParamValidator and
|
382
|
+
### re-validate the resulting values.
|
383
|
+
def merge!( params )
|
384
|
+
return if params.empty?
|
385
|
+
|
386
|
+
self.log.debug "Merging parameters for revalidation: %p" % [ params ]
|
387
|
+
@missing_fields.clear
|
388
|
+
@unknown_fields.clear
|
389
|
+
@required_fields.clear
|
390
|
+
@invalid_fields.clear
|
391
|
+
@untaint_fields.clear
|
392
|
+
@require_some_fields.clear
|
393
|
+
@optional_fields.clear
|
394
|
+
@form.clear
|
395
|
+
|
396
|
+
newparams = @raw_form.merge( params )
|
397
|
+
@raw_form.clear
|
398
|
+
|
399
|
+
self.log.debug " merged raw form is: %p" % [ newparams ]
|
400
|
+
self.validate( newparams )
|
401
|
+
end
|
402
|
+
|
403
|
+
|
404
|
+
### Returns an array containing valid parameters in the validator corresponding to the
|
405
|
+
### given +selector+(s).
|
406
|
+
def values_at( *selector )
|
407
|
+
selector.map!( &:to_s )
|
408
|
+
return @form.values_at( *selector )
|
409
|
+
end
|
410
|
+
|
411
|
+
|
412
|
+
#
|
413
|
+
# :section: Constraint methods
|
414
|
+
#
|
415
|
+
|
416
|
+
### Try to match the specified +val+ using the built-in constraint pattern
|
417
|
+
### associated with +name+, returning the matched value upon success, and +nil+
|
418
|
+
### if the +val+ didn't match. If a +block+ is given, it's called with the
|
419
|
+
### associated MatchData on success, and its return value is returned instead of
|
420
|
+
### the matching String.
|
421
|
+
def match_builtin_constraint( val, name )
|
422
|
+
self.log.debug "Validating %p using built-in constraint %p" % [ val, name ]
|
423
|
+
re = self.class.pattern_for_constraint( name.to_sym )
|
424
|
+
match = re.match( val ) or return nil
|
425
|
+
self.log.debug " matched: %p" % [ match ]
|
426
|
+
|
427
|
+
if block_given?
|
428
|
+
begin
|
429
|
+
return yield( match )
|
430
|
+
rescue ArgumentError
|
431
|
+
return nil
|
432
|
+
end
|
433
|
+
else
|
434
|
+
return match.to_s
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
|
439
|
+
### Constrain a value to +true+ (or +yes+) and +false+ (or +no+).
|
440
|
+
def match_boolean( val )
|
441
|
+
return self.match_builtin_constraint( val, :boolean ) do |m|
|
442
|
+
m.to_s.start_with?( 'y', 't', '1' )
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
|
447
|
+
### Constrain a value to an integer
|
448
|
+
def match_integer( val )
|
449
|
+
return self.match_builtin_constraint( val, :integer ) do |m|
|
450
|
+
Integer( m.to_s )
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
|
455
|
+
### Contrain a value to a Float
|
456
|
+
def match_float( val )
|
457
|
+
return self.match_builtin_constraint( val, :float ) do |m|
|
458
|
+
Float( m.to_s )
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
|
463
|
+
### Constrain a value to a parseable Date
|
464
|
+
def match_date( val )
|
465
|
+
return Date.parse( val ) rescue nil
|
466
|
+
end
|
467
|
+
|
468
|
+
|
469
|
+
### Constrain a value to alpha characters (a-z, case-insensitive)
|
470
|
+
def match_alpha( val )
|
471
|
+
return self.match_builtin_constraint( val, :alpha )
|
472
|
+
end
|
473
|
+
|
474
|
+
|
475
|
+
### Constrain a value to alpha characters (a-z, case-insensitive and 0-9)
|
476
|
+
def match_alphanumeric( val )
|
477
|
+
return self.match_builtin_constraint( val, :alphanumeric )
|
478
|
+
end
|
479
|
+
|
480
|
+
|
481
|
+
### Constrain a value to any printable characters + whitespace, newline, and CR.
|
482
|
+
def match_printable( val )
|
483
|
+
return self.match_builtin_constraint( val, :printable )
|
484
|
+
end
|
485
|
+
alias_method :match_string, :match_printable
|
486
|
+
|
487
|
+
|
488
|
+
### Constrain a value to any UTF-8 word characters.
|
489
|
+
def match_word( val )
|
490
|
+
return self.match_builtin_constraint( val, :word )
|
491
|
+
end
|
492
|
+
|
493
|
+
|
494
|
+
### Override the parent class's definition to (not-sloppily) match email
|
495
|
+
### addresses.
|
496
|
+
def match_email( val )
|
497
|
+
return self.match_builtin_constraint( val, :email )
|
498
|
+
end
|
499
|
+
|
500
|
+
|
501
|
+
### Match valid hostnames according to the rules of the URL RFC.
|
502
|
+
def match_hostname( val )
|
503
|
+
return self.match_builtin_constraint( val, :hostname )
|
504
|
+
end
|
505
|
+
|
506
|
+
|
507
|
+
### Match valid URIs
|
508
|
+
def match_uri( val )
|
509
|
+
return self.match_builtin_constraint( val, :uri ) do |m|
|
510
|
+
URI.parse( m.to_s )
|
511
|
+
end
|
512
|
+
rescue URI::InvalidURIError => err
|
513
|
+
self.log.error "Error trying to parse URI %p: %s" % [ val, err.message ]
|
514
|
+
return nil
|
515
|
+
rescue NoMethodError
|
516
|
+
self.log.debug "Ignoring bug in URI#parse"
|
517
|
+
return nil
|
518
|
+
end
|
519
|
+
|
520
|
+
|
521
|
+
### Apply one or more +constraints+ to the field value/s corresponding to
|
522
|
+
### +key+.
|
523
|
+
def do_constraint( key, constraints )
|
524
|
+
constraints.each do |constraint|
|
525
|
+
case constraint
|
526
|
+
when String
|
527
|
+
apply_string_constraint( key, constraint )
|
528
|
+
when Hash
|
529
|
+
apply_hash_constraint( key, constraint )
|
530
|
+
when Proc, Method
|
531
|
+
apply_proc_constraint( key, constraint )
|
532
|
+
when Regexp
|
533
|
+
apply_regexp_constraint( key, constraint )
|
534
|
+
else
|
535
|
+
raise "unknown constraint type %p" % [constraint]
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
|
541
|
+
### Applies a builtin constraint to form[key].
|
542
|
+
def apply_string_constraint( key, constraint )
|
543
|
+
# FIXME: multiple elements
|
544
|
+
rval = self.__send__( "match_#{constraint}", @form[key].to_s )
|
545
|
+
self.log.debug "Tried a string constraint: %p: %p" %
|
546
|
+
[ @form[key].to_s, rval ]
|
547
|
+
self.set_form_value( key, rval, constraint )
|
548
|
+
end
|
549
|
+
|
550
|
+
|
551
|
+
### Apply a constraint given as a Hash to the value/s corresponding to the
|
552
|
+
### specified +key+:
|
553
|
+
###
|
554
|
+
### constraint::
|
555
|
+
### A builtin constraint (as a Symbol; e.g., :email), a Regexp, or a Proc.
|
556
|
+
### name::
|
557
|
+
### A description of the constraint should it fail and be listed in #invalid.
|
558
|
+
### params::
|
559
|
+
### If +constraint+ is a Proc, this field should contain a list of other
|
560
|
+
### fields to send to the Proc.
|
561
|
+
def apply_hash_constraint( key, constraint )
|
562
|
+
action = constraint["constraint"]
|
563
|
+
|
564
|
+
rval = case action
|
565
|
+
when String
|
566
|
+
self.apply_string_constraint( key, action )
|
567
|
+
when Regexp
|
568
|
+
self.apply_regexp_constraint( key, action )
|
569
|
+
when Proc
|
570
|
+
if args = constraint["params"]
|
571
|
+
args.collect! {|field| @form[field] }
|
572
|
+
self.apply_proc_constraint( key, action, *args )
|
573
|
+
else
|
574
|
+
self.apply_proc_constraint( key, action )
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
# If the validation failed, and there's a name for this constraint, replace
|
579
|
+
# the name in @invalid_fields with the name
|
580
|
+
if !rval && constraint["name"]
|
581
|
+
@invalid_fields[ key ] = constraint["name"]
|
582
|
+
end
|
583
|
+
|
584
|
+
return rval
|
585
|
+
end
|
586
|
+
|
587
|
+
|
588
|
+
### Apply a constraint that was specified as a Proc to the value for the given
|
589
|
+
### +key+
|
590
|
+
def apply_proc_constraint( key, constraint, *params )
|
591
|
+
value = nil
|
592
|
+
|
593
|
+
unless params.empty?
|
594
|
+
value = constraint.to_proc.call( *params )
|
595
|
+
else
|
596
|
+
value = constraint.to_proc.call( @form[key] )
|
597
|
+
end
|
598
|
+
|
599
|
+
self.set_form_value( key, value, constraint )
|
600
|
+
rescue => err
|
601
|
+
self.log.error "%p while validating %p using %p: %s (from %s)" %
|
602
|
+
[ err.class, key, constraint, err.message, err.backtrace.first ]
|
603
|
+
self.set_form_value( key, nil, constraint )
|
604
|
+
end
|
605
|
+
|
606
|
+
|
607
|
+
### Applies regexp constraint to form[key]
|
608
|
+
def apply_regexp_constraint( key, constraint )
|
609
|
+
self.log.debug "Validating %p via regexp %p" % [ @form[key], constraint ]
|
610
|
+
|
611
|
+
if match = constraint.match( @form[key].to_s )
|
612
|
+
self.log.debug " matched %p" % [match[0]]
|
613
|
+
|
614
|
+
if match.captures.empty?
|
615
|
+
self.log.debug " no captures, using whole match: %p" % [match[0]]
|
616
|
+
self.set_form_value( key, match[0], constraint )
|
617
|
+
elsif match.names.length > 1
|
618
|
+
self.log.debug " extracting hash of named captures: %p" % [ match.names ]
|
619
|
+
hash = match.names.inject( {} ) do |accum,name|
|
620
|
+
accum[ name.to_sym ] = match[ name ]
|
621
|
+
accum
|
622
|
+
end
|
623
|
+
|
624
|
+
self.set_form_value( key, hash, constraint )
|
625
|
+
elsif match.captures.length == 1
|
626
|
+
self.log.debug " extracting one capture: %p" % [match.captures.first]
|
627
|
+
self.set_form_value( key, match.captures.first, constraint )
|
628
|
+
else
|
629
|
+
self.log.debug " extracting multiple captures: %p" % [match.captures]
|
630
|
+
self.set_form_value( key, match.captures, constraint )
|
631
|
+
end
|
632
|
+
else
|
633
|
+
self.set_form_value( key, nil, constraint )
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
|
638
|
+
### Set the form value for the given +key+. If +value+ is false, add it to
|
639
|
+
### the list of invalid fields with a description derived from the specified
|
640
|
+
### +constraint+.
|
641
|
+
def set_form_value( key, value, constraint )
|
642
|
+
key.untaint
|
643
|
+
|
644
|
+
# Have to test for nil because valid values might be false.
|
645
|
+
if !value.nil?
|
646
|
+
self.log.debug "Setting form value for %p to %p (constraint was %p)" %
|
647
|
+
[ key, value, constraint ]
|
648
|
+
if self.untaint?( key )
|
649
|
+
if value.respond_to?( :each_value )
|
650
|
+
value.each_value( &:untaint )
|
651
|
+
elsif value.is_a?( Array )
|
652
|
+
value.each( &:untaint )
|
653
|
+
else
|
654
|
+
value.untaint
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
658
|
+
@form[key] = value
|
659
|
+
return true
|
660
|
+
|
661
|
+
else
|
662
|
+
self.log.debug "Clearing form value for %p (constraint was %p)" %
|
663
|
+
[ key, constraint ]
|
664
|
+
@form.delete( key )
|
665
|
+
@invalid_fields ||= {}
|
666
|
+
@invalid_fields[ key ] ||= []
|
667
|
+
|
668
|
+
unless @invalid_fields[ key ].include?( constraint )
|
669
|
+
@invalid_fields[ key ].push( constraint )
|
670
|
+
end
|
671
|
+
return false
|
672
|
+
end
|
673
|
+
end
|
674
|
+
|
675
|
+
|
676
|
+
### Formvalidator hack:
|
677
|
+
### The formvalidator filters method has a bug where he assumes an array
|
678
|
+
### when it is in fact a string for multiple values (ie anytime you have a
|
679
|
+
### text-area with newlines in it).
|
680
|
+
def filters
|
681
|
+
@filters_array = Array(@profile[:filters]) unless(@filters_array)
|
682
|
+
@filters_array.each do |filter|
|
683
|
+
|
684
|
+
if respond_to?( "filter_#{filter}" )
|
685
|
+
@form.keys.each do |field|
|
686
|
+
# If a key has multiple elements, apply filter to each element
|
687
|
+
@field_array = Array( @form[field] )
|
688
|
+
|
689
|
+
if @field_array.length > 1
|
690
|
+
@field_array.each_index do |i|
|
691
|
+
elem = @field_array[i]
|
692
|
+
@field_array[i] = self.send("filter_#{filter}", elem)
|
693
|
+
end
|
694
|
+
else
|
695
|
+
if not @form[field].to_s.empty?
|
696
|
+
@form[field] = self.send("filter_#{filter}", @form[field].to_s)
|
697
|
+
end
|
698
|
+
end
|
699
|
+
end
|
700
|
+
end
|
701
|
+
end
|
702
|
+
@form
|
703
|
+
end
|
704
|
+
|
705
|
+
|
706
|
+
#######
|
707
|
+
private
|
708
|
+
#######
|
709
|
+
|
710
|
+
### Overridden to eliminate use of default #to_a (deprecated)
|
711
|
+
def strify_array( array )
|
712
|
+
array = [ array ] if !array.is_a?( Array )
|
713
|
+
array.map do |m|
|
714
|
+
m = (Array === m) ? strify_array(m) : m
|
715
|
+
m = (Hash === m) ? strify_hash(m) : m
|
716
|
+
Symbol === m ? m.to_s : m
|
717
|
+
end
|
718
|
+
end
|
719
|
+
|
720
|
+
|
721
|
+
### Build a deep hash out of the given parameter +value+
|
722
|
+
def build_deep_hash( value, hash, levels )
|
723
|
+
if levels.length == 0
|
724
|
+
value.untaint
|
725
|
+
elsif hash.nil?
|
726
|
+
{ levels.first => build_deep_hash(value, nil, levels[1..-1]) }
|
727
|
+
else
|
728
|
+
hash.update({ levels.first => build_deep_hash(value, hash[levels.first], levels[1..-1]) })
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
732
|
+
|
733
|
+
### Get the number of hash levels in the specified +key+
|
734
|
+
### Stolen from the CGIMethods class in Rails' action_controller.
|
735
|
+
def get_levels( key )
|
736
|
+
all, main, bracketed, trailing = PARAMS_HASH_RE.match( key ).to_a
|
737
|
+
if main.nil?
|
738
|
+
return []
|
739
|
+
elsif trailing
|
740
|
+
return [key.untaint]
|
741
|
+
elsif bracketed
|
742
|
+
return [main.untaint] + bracketed.slice(1...-1).split('][').collect {|k| k.untaint }
|
743
|
+
else
|
744
|
+
return [main.untaint]
|
745
|
+
end
|
746
|
+
end
|
747
|
+
|
748
|
+
end # class Strelka::App::ParamValidator
|
749
|
+
|
750
|
+
|
751
|
+
|