arrow 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ChangeLog +1590 -0
- data/LICENSE +28 -0
- data/README +75 -0
- data/Rakefile +366 -0
- data/Rakefile.local +63 -0
- data/data/arrow/applets/TEMPLATE.rb.tpl +53 -0
- data/data/arrow/applets/args.rb +50 -0
- data/data/arrow/applets/config.rb +55 -0
- data/data/arrow/applets/error.rb +63 -0
- data/data/arrow/applets/files.rb +46 -0
- data/data/arrow/applets/inspect.rb +46 -0
- data/data/arrow/applets/nosuchapplet.rb +31 -0
- data/data/arrow/applets/status.rb +92 -0
- data/data/arrow/applets/test.rb +133 -0
- data/data/arrow/applets/tutorial/counter.rb +96 -0
- data/data/arrow/applets/tutorial/dingus.rb +67 -0
- data/data/arrow/applets/tutorial/hello.rb +34 -0
- data/data/arrow/applets/tutorial/hello2.rb +73 -0
- data/data/arrow/applets/tutorial/imgtext.rb +90 -0
- data/data/arrow/applets/tutorial/imgtext2.rb +286 -0
- data/data/arrow/applets/tutorial/index.rb +36 -0
- data/data/arrow/applets/tutorial/logo.rb +98 -0
- data/data/arrow/applets/tutorial/memcache.rb +61 -0
- data/data/arrow/applets/tutorial/missing.rb +37 -0
- data/data/arrow/applets/tutorial/protected.rb +100 -0
- data/data/arrow/applets/tutorial/redirector.rb +52 -0
- data/data/arrow/applets/tutorial/rndimages.rb +159 -0
- data/data/arrow/applets/tutorial/sharenotes.rb +83 -0
- data/data/arrow/applets/tutorial/subclassed-hello.rb +32 -0
- data/data/arrow/applets/tutorial/superhello.rb +72 -0
- data/data/arrow/applets/tutorial/timeclock.rb +78 -0
- data/data/arrow/applets/view-applet.rb +123 -0
- data/data/arrow/applets/view-template.rb +85 -0
- data/data/arrow/applets/wiki.rb +274 -0
- data/data/arrow/templates/TEMPLATE.tmpl.tpl +36 -0
- data/data/arrow/templates/applet-status.tmpl +153 -0
- data/data/arrow/templates/args-display.tmpl +120 -0
- data/data/arrow/templates/config/display-table.tmpl +36 -0
- data/data/arrow/templates/config/display.tmpl +36 -0
- data/data/arrow/templates/counter-deleted.tmpl +33 -0
- data/data/arrow/templates/counter.tmpl +59 -0
- data/data/arrow/templates/dingus.tmpl +55 -0
- data/data/arrow/templates/enumtable.tmpl +8 -0
- data/data/arrow/templates/error-display.tmpl +92 -0
- data/data/arrow/templates/filemap.tmpl +89 -0
- data/data/arrow/templates/hello-world-src.tmpl +34 -0
- data/data/arrow/templates/hello-world.tmpl +60 -0
- data/data/arrow/templates/imgtext/fontlist.tmpl +46 -0
- data/data/arrow/templates/imgtext/form.tmpl +70 -0
- data/data/arrow/templates/imgtext/reload-error.tmpl +40 -0
- data/data/arrow/templates/imgtext/reload.tmpl +55 -0
- data/data/arrow/templates/inspect/display.tmpl +80 -0
- data/data/arrow/templates/loginform.tmpl +64 -0
- data/data/arrow/templates/logout.tmpl +32 -0
- data/data/arrow/templates/memcache/display.tmpl +41 -0
- data/data/arrow/templates/navbar.incl +27 -0
- data/data/arrow/templates/nosuchapplet.tmpl +32 -0
- data/data/arrow/templates/printsource.tmpl +35 -0
- data/data/arrow/templates/protected.tmpl +36 -0
- data/data/arrow/templates/rndimages.tmpl +38 -0
- data/data/arrow/templates/service-response.tmpl +13 -0
- data/data/arrow/templates/sharenotes/display.tmpl +38 -0
- data/data/arrow/templates/status.tmpl +120 -0
- data/data/arrow/templates/templateviewer.tmpl +43 -0
- data/data/arrow/templates/test/harness.tmpl +57 -0
- data/data/arrow/templates/test/list.tmpl +48 -0
- data/data/arrow/templates/test/problem.tmpl +42 -0
- data/data/arrow/templates/tutorial/index.tmpl +37 -0
- data/data/arrow/templates/tutorial/missingapplet.tmpl +29 -0
- data/data/arrow/templates/view-applet-nosuch.tmpl +32 -0
- data/data/arrow/templates/view-applet.tmpl +40 -0
- data/data/arrow/templates/view-template.tmpl +83 -0
- data/data/arrow/templates/wiki/formerror.tmpl +47 -0
- data/data/arrow/templates/wiki/markup_help.incl +6 -0
- data/data/arrow/templates/wiki/new.tmpl +56 -0
- data/data/arrow/templates/wiki/new_system.tmpl +122 -0
- data/data/arrow/templates/wiki/sectionlist.tmpl +43 -0
- data/data/arrow/templates/wiki/show.tmpl +34 -0
- data/docs/manual/layouts/default.page +43 -0
- data/docs/manual/lib/api-filter.rb +81 -0
- data/docs/manual/lib/editorial-filter.rb +64 -0
- data/docs/manual/lib/examples-filter.rb +244 -0
- data/docs/manual/lib/links-filter.rb +117 -0
- data/lib/apache/fakerequest.rb +448 -0
- data/lib/apache/logger.rb +33 -0
- data/lib/arrow.rb +51 -0
- data/lib/arrow/acceptparam.rb +207 -0
- data/lib/arrow/applet.rb +725 -0
- data/lib/arrow/appletmixins.rb +218 -0
- data/lib/arrow/appletregistry.rb +590 -0
- data/lib/arrow/applettestcase.rb +503 -0
- data/lib/arrow/broker.rb +255 -0
- data/lib/arrow/cache.rb +176 -0
- data/lib/arrow/config-loaders/yaml.rb +75 -0
- data/lib/arrow/config.rb +615 -0
- data/lib/arrow/constants.rb +24 -0
- data/lib/arrow/cookie.rb +359 -0
- data/lib/arrow/cookieset.rb +108 -0
- data/lib/arrow/dispatcher.rb +368 -0
- data/lib/arrow/dispatcherloader.rb +50 -0
- data/lib/arrow/exceptions.rb +61 -0
- data/lib/arrow/fallbackhandler.rb +48 -0
- data/lib/arrow/formvalidator.rb +631 -0
- data/lib/arrow/htmltokenizer.rb +343 -0
- data/lib/arrow/logger.rb +488 -0
- data/lib/arrow/logger/apacheoutputter.rb +69 -0
- data/lib/arrow/logger/arrayoutputter.rb +63 -0
- data/lib/arrow/logger/coloroutputter.rb +111 -0
- data/lib/arrow/logger/fileoutputter.rb +96 -0
- data/lib/arrow/logger/htmloutputter.rb +54 -0
- data/lib/arrow/logger/outputter.rb +123 -0
- data/lib/arrow/mixins.rb +425 -0
- data/lib/arrow/monkeypatches.rb +94 -0
- data/lib/arrow/object.rb +117 -0
- data/lib/arrow/path.rb +196 -0
- data/lib/arrow/service.rb +447 -0
- data/lib/arrow/session.rb +289 -0
- data/lib/arrow/session/dbstore.rb +100 -0
- data/lib/arrow/session/filelock.rb +160 -0
- data/lib/arrow/session/filestore.rb +132 -0
- data/lib/arrow/session/id.rb +98 -0
- data/lib/arrow/session/lock.rb +253 -0
- data/lib/arrow/session/md5id.rb +42 -0
- data/lib/arrow/session/nulllock.rb +42 -0
- data/lib/arrow/session/posixlock.rb +166 -0
- data/lib/arrow/session/sha1id.rb +54 -0
- data/lib/arrow/session/store.rb +366 -0
- data/lib/arrow/session/usertrackid.rb +52 -0
- data/lib/arrow/spechelpers.rb +73 -0
- data/lib/arrow/template.rb +713 -0
- data/lib/arrow/template/attr.rb +31 -0
- data/lib/arrow/template/call.rb +31 -0
- data/lib/arrow/template/comment.rb +33 -0
- data/lib/arrow/template/container.rb +118 -0
- data/lib/arrow/template/else.rb +41 -0
- data/lib/arrow/template/elsif.rb +44 -0
- data/lib/arrow/template/escape.rb +53 -0
- data/lib/arrow/template/export.rb +87 -0
- data/lib/arrow/template/for.rb +145 -0
- data/lib/arrow/template/if.rb +78 -0
- data/lib/arrow/template/import.rb +119 -0
- data/lib/arrow/template/include.rb +206 -0
- data/lib/arrow/template/iterator.rb +208 -0
- data/lib/arrow/template/nodes.rb +734 -0
- data/lib/arrow/template/parser.rb +571 -0
- data/lib/arrow/template/prettyprint.rb +53 -0
- data/lib/arrow/template/render.rb +191 -0
- data/lib/arrow/template/selectlist.rb +94 -0
- data/lib/arrow/template/set.rb +87 -0
- data/lib/arrow/template/timedelta.rb +81 -0
- data/lib/arrow/template/unless.rb +78 -0
- data/lib/arrow/template/urlencode.rb +51 -0
- data/lib/arrow/template/yield.rb +139 -0
- data/lib/arrow/templatefactory.rb +125 -0
- data/lib/arrow/testcase.rb +567 -0
- data/lib/arrow/transaction.rb +608 -0
- data/rake/191_compat.rb +26 -0
- data/rake/dependencies.rb +76 -0
- data/rake/documentation.rb +114 -0
- data/rake/helpers.rb +502 -0
- data/rake/hg.rb +282 -0
- data/rake/manual.rb +787 -0
- data/rake/packaging.rb +129 -0
- data/rake/publishing.rb +278 -0
- data/rake/style.rb +62 -0
- data/rake/svn.rb +668 -0
- data/rake/testing.rb +187 -0
- data/rake/verifytask.rb +64 -0
- data/spec/arrow/acceptparam_spec.rb +157 -0
- data/spec/arrow/applet_spec.rb +575 -0
- data/spec/arrow/appletmixins_spec.rb +409 -0
- data/spec/arrow/appletregistry_spec.rb +294 -0
- data/spec/arrow/broker_spec.rb +153 -0
- data/spec/arrow/config_spec.rb +224 -0
- data/spec/arrow/cookieset_spec.rb +164 -0
- data/spec/arrow/dispatcher_spec.rb +137 -0
- data/spec/arrow/dispatcherloader_spec.rb +65 -0
- data/spec/arrow/formvalidator_spec.rb +781 -0
- data/spec/arrow/logger_spec.rb +346 -0
- data/spec/arrow/mixins_spec.rb +120 -0
- data/spec/arrow/service_spec.rb +645 -0
- data/spec/arrow/session_spec.rb +121 -0
- data/spec/arrow/template/iterator_spec.rb +222 -0
- data/spec/arrow/templatefactory_spec.rb +185 -0
- data/spec/arrow/transaction_spec.rb +319 -0
- data/spec/arrow_spec.rb +37 -0
- data/spec/lib/appletmatchers.rb +281 -0
- data/spec/lib/constants.rb +77 -0
- data/spec/lib/helpers.rb +41 -0
- data/spec/lib/matchers.rb +44 -0
- data/tests/cookie.tests.rb +310 -0
- data/tests/path.tests.rb +157 -0
- data/tests/session.tests.rb +111 -0
- data/tests/session_id.tests.rb +82 -0
- data/tests/session_lock.tests.rb +191 -0
- data/tests/session_store.tests.rb +53 -0
- data/tests/template.tests.rb +1360 -0
- metadata +339 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'arrow'
|
4
|
+
|
5
|
+
#
|
6
|
+
# The Arrow::FallbackHandler class, a request handler for Arrow that is used to
|
7
|
+
# handle misconfigured handler requests.
|
8
|
+
#
|
9
|
+
# == VCS Id
|
10
|
+
#
|
11
|
+
# $Id$
|
12
|
+
#
|
13
|
+
# == Authors
|
14
|
+
#
|
15
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
16
|
+
#
|
17
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
18
|
+
#
|
19
|
+
class Arrow::FallbackHandler
|
20
|
+
|
21
|
+
### Create a new instance for the given +key+ and +instances+.
|
22
|
+
def initialize( key, instances )
|
23
|
+
@key = key
|
24
|
+
@instances = instances
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
### Handle a request with output that explains what the problem is.
|
29
|
+
def handler( req )
|
30
|
+
req.content_type = "text/plain"
|
31
|
+
req.send_http_header
|
32
|
+
req.print <<-EOF
|
33
|
+
|
34
|
+
Arrow Configuration Error
|
35
|
+
|
36
|
+
This URL is configured to be handled by the dispatcher keyed with '#{@key.inspect}',
|
37
|
+
but there was no dispatcher associated with that key. The instances I know about
|
38
|
+
are:
|
39
|
+
|
40
|
+
#{@instances.collect {|k,d| "-- #{k.inspect} --\n\n#{d.inspect}"}.join("\n\n")}
|
41
|
+
|
42
|
+
EOF
|
43
|
+
|
44
|
+
return Apache::OK
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
@@ -0,0 +1,631 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
require 'uri'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'date'
|
7
|
+
require 'formvalidator'
|
8
|
+
|
9
|
+
require 'arrow/mixins'
|
10
|
+
require 'arrow/exceptions'
|
11
|
+
require 'arrow/object'
|
12
|
+
|
13
|
+
# A FormValidator variant that adds some convenience methods and additional validations.
|
14
|
+
#
|
15
|
+
# == Usage
|
16
|
+
#
|
17
|
+
# require 'arrow/formvalidator'
|
18
|
+
#
|
19
|
+
# # Profile specifies validation criteria for input
|
20
|
+
# profile = {
|
21
|
+
# :required => :name,
|
22
|
+
# :optional => [:email, :description],
|
23
|
+
# :filters => [:strip, :squeeze],
|
24
|
+
# :untaint_all_constraints => true,
|
25
|
+
# :descriptions => {
|
26
|
+
# :email => "Customer Email",
|
27
|
+
# :description => "Issue Description",
|
28
|
+
# :name => "Customer Name",
|
29
|
+
# },
|
30
|
+
# :constraints => {
|
31
|
+
# :email => :email,
|
32
|
+
# :name => /^[\x20-\x7f]+$/,
|
33
|
+
# :description => /^[\x20-\x7f]+$/,
|
34
|
+
# },
|
35
|
+
# }
|
36
|
+
#
|
37
|
+
# # Create a validator object and pass in a hash of request parameters and the
|
38
|
+
# # profile hash.
|
39
|
+
# validator = Arrow::FormValidator.new
|
40
|
+
# validator.validate( req_params, profile )
|
41
|
+
#
|
42
|
+
# # Now if there weren't any errors, send the success page
|
43
|
+
# if validator.okay?
|
44
|
+
# return success_template
|
45
|
+
#
|
46
|
+
# # Otherwise fill in the error template with auto-generated error messages
|
47
|
+
# # and return that instead.
|
48
|
+
# else
|
49
|
+
# failure_template.errors( validator.error_messages )
|
50
|
+
# return failure_template
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# == VCS Id
|
54
|
+
#
|
55
|
+
# $Id: formvalidator.rb,v 1b8226c06192 2010/08/09 17:50:38 ged $
|
56
|
+
#
|
57
|
+
# == Authors
|
58
|
+
#
|
59
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
60
|
+
#
|
61
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
62
|
+
#
|
63
|
+
#
|
64
|
+
# Portions of this file are from Ruby on Rails' CGIMethods class from the
|
65
|
+
# action_controller:
|
66
|
+
#
|
67
|
+
# Copyright (c) 2004 David Heinemeier Hansson
|
68
|
+
#
|
69
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
70
|
+
# a copy of this software and associated documentation files (the
|
71
|
+
# "Software"), to deal in the Software without restriction, including
|
72
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
73
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
74
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
75
|
+
# the following conditions:
|
76
|
+
#
|
77
|
+
# The above copyright notice and this permission notice shall be
|
78
|
+
# included in all copies or substantial portions of the Software.
|
79
|
+
#
|
80
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
81
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
82
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
83
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
84
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
85
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
86
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
87
|
+
#
|
88
|
+
#
|
89
|
+
#--
|
90
|
+
#
|
91
|
+
# Please see the file COPYRIGHT in the 'docs' directory for licensing details.
|
92
|
+
#
|
93
|
+
class Arrow::FormValidator < ::FormValidator
|
94
|
+
extend Forwardable
|
95
|
+
include Arrow::Loggable
|
96
|
+
|
97
|
+
|
98
|
+
Defaults = {
|
99
|
+
:descriptions => {},
|
100
|
+
}
|
101
|
+
|
102
|
+
|
103
|
+
### Create a new Arrow::FormValidator object.
|
104
|
+
def initialize( profile, params=nil )
|
105
|
+
@profile = Defaults.merge( profile )
|
106
|
+
validate( params ) if params
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
######
|
111
|
+
public
|
112
|
+
######
|
113
|
+
|
114
|
+
attr_reader :raw_form
|
115
|
+
|
116
|
+
|
117
|
+
### Delegate Hash methods to the valid form variables hash
|
118
|
+
def_delegators :@form,
|
119
|
+
*(Hash.public_instance_methods(false) - ['[]', '[]=', 'inspect'])
|
120
|
+
|
121
|
+
|
122
|
+
### Stringified description of the validator
|
123
|
+
def to_s
|
124
|
+
""
|
125
|
+
end
|
126
|
+
|
127
|
+
### Hash of field descriptions
|
128
|
+
def descriptions
|
129
|
+
@profile[:descriptions]
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
### Set hash of field descriptions
|
134
|
+
def descriptions=( new_descs )
|
135
|
+
@profile[:descriptions] = new_descs
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
### Validate the input in +params+. If the optional +additional_profile+ is
|
140
|
+
### given, merge it with the validator's default profile before validating.
|
141
|
+
def validate( params, additional_profile=nil )
|
142
|
+
@raw_form = params.dup
|
143
|
+
profile = @profile
|
144
|
+
|
145
|
+
if additional_profile
|
146
|
+
self.log.debug "Merging additional profile %p" % [additional_profile]
|
147
|
+
profile = @profile.merge( additional_profile )
|
148
|
+
end
|
149
|
+
|
150
|
+
super( params, profile )
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
### Overridden to remove the check for extra keys.
|
155
|
+
def check_profile_syntax( profile )
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
### Index operator; fetch the validated value for form field +key+.
|
160
|
+
def []( key )
|
161
|
+
@form[ key.to_s ]
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
### Index assignment operator; set the validated value for form field +key+
|
166
|
+
### to the specified +val+.
|
167
|
+
def []=( key, val )
|
168
|
+
@form[ key.to_s ] = val
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
### Returns +true+ if there were no arguments given.
|
173
|
+
def empty?
|
174
|
+
return @form.empty?
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
### Returns +true+ if there were arguments given.
|
179
|
+
def args?
|
180
|
+
return !@form.empty?
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
### Returns +true+ if any fields are missing or contain invalid values.
|
185
|
+
def errors?
|
186
|
+
return !self.okay?
|
187
|
+
end
|
188
|
+
alias_method :has_errors?, :errors?
|
189
|
+
|
190
|
+
|
191
|
+
### Return +true+ if all required fields were present and validated
|
192
|
+
### correctly.
|
193
|
+
def okay?
|
194
|
+
self.missing.empty? && self.invalid.empty?
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
### Returns +true+ if the given +field+ is one that should be untainted.
|
199
|
+
def untaint?( field )
|
200
|
+
self.log.debug "Checking to see if %p should be untainted." % [field]
|
201
|
+
rval = ( @untaint_all || @untaint_fields.include?(field) )
|
202
|
+
if rval
|
203
|
+
self.log.debug " ...yep it should."
|
204
|
+
else
|
205
|
+
self.log.debug " ...nope."
|
206
|
+
end
|
207
|
+
|
208
|
+
return rval
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
|
213
|
+
### Return an array of field names which had some kind of error associated
|
214
|
+
### with them.
|
215
|
+
def error_fields
|
216
|
+
return self.missing | self.invalid.keys
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
### Get the description for the specified field.
|
221
|
+
def get_description( field )
|
222
|
+
return @profile[:descriptions][ field.to_s ] if
|
223
|
+
@profile[:descriptions].key?( field.to_s )
|
224
|
+
|
225
|
+
desc = field.to_s.
|
226
|
+
gsub( /.*\[(\w+)\]/, "\\1" ).
|
227
|
+
gsub( /_(.)/ ) {|m| " " + m[1,1].upcase }.
|
228
|
+
gsub( /^(.)/ ) {|m| m.upcase }
|
229
|
+
return desc
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
### Return an error message for each missing or invalid field; if
|
234
|
+
### +includeUnknown+ is +true+, also include messages for unknown fields.
|
235
|
+
def error_messages( include_unknown=false )
|
236
|
+
self.log.debug "Building error messages from descriptions: %p" %
|
237
|
+
[ @profile[:descriptions] ]
|
238
|
+
msgs = []
|
239
|
+
self.missing.each do |field|
|
240
|
+
msgs << "Missing value for '%s'" % self.get_description( field )
|
241
|
+
end
|
242
|
+
|
243
|
+
self.invalid.each do |field, constraint|
|
244
|
+
msgs << "Invalid value for '%s'" % self.get_description( field )
|
245
|
+
end
|
246
|
+
|
247
|
+
if include_unknown
|
248
|
+
self.unknown.each do |field|
|
249
|
+
msgs << "Unknown parameter '%s'" % self.get_description( field )
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
return msgs
|
254
|
+
end
|
255
|
+
|
256
|
+
|
257
|
+
### Returns a distinct list of missing fields. Overridden to eliminate the
|
258
|
+
### "undefined method `<=>' for :foo:Symbol" error.
|
259
|
+
def missing
|
260
|
+
@missing_fields.uniq.sort_by {|f| f.to_s}
|
261
|
+
end
|
262
|
+
|
263
|
+
### Returns a distinct list of unknown fields.
|
264
|
+
def unknown
|
265
|
+
(@unknown_fields - @invalid_fields.keys).uniq.sort_by {|f| f.to_s}
|
266
|
+
end
|
267
|
+
|
268
|
+
|
269
|
+
### Returns the valid fields after expanding Rails-style
|
270
|
+
### 'customer[address][street]' variables into multi-level hashes.
|
271
|
+
def valid
|
272
|
+
if @parsed_params.nil?
|
273
|
+
@parsed_params = {}
|
274
|
+
valid = super()
|
275
|
+
|
276
|
+
for key, value in valid
|
277
|
+
value = [value] if key =~ /.*\[\]$/
|
278
|
+
unless key.include?( '[' )
|
279
|
+
@parsed_params[ key ] = value
|
280
|
+
else
|
281
|
+
build_deep_hash( value, @parsed_params, get_levels(key) )
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
return @parsed_params
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
### Constraint methods
|
291
|
+
|
292
|
+
### Constrain a value to +true+ (or +yes+) and +false+ (or +no+).
|
293
|
+
def match_boolean( val )
|
294
|
+
rval = nil
|
295
|
+
if ( val =~ /^(t(?:rue)?|y(?:es)?)|1$/i )
|
296
|
+
rval = true
|
297
|
+
elsif ( val =~ /^(no?|f(?:alse)?)|0$/i )
|
298
|
+
rval = false
|
299
|
+
end
|
300
|
+
|
301
|
+
return rval
|
302
|
+
end
|
303
|
+
|
304
|
+
|
305
|
+
### Constrain a value to an integer
|
306
|
+
def match_integer( val )
|
307
|
+
return Integer( val ) rescue nil
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
### Contrain a value to a Float
|
312
|
+
def match_float( val )
|
313
|
+
return Float( val ) rescue nil
|
314
|
+
end
|
315
|
+
|
316
|
+
|
317
|
+
### Constrain a value to a parseable Date
|
318
|
+
def match_date( val )
|
319
|
+
return Date.parse( val ) rescue nil
|
320
|
+
end
|
321
|
+
|
322
|
+
|
323
|
+
### Constrain a value to alpha characters (a-z, case-insensitive)
|
324
|
+
def match_alpha( val )
|
325
|
+
if val =~ /^([a-z]+)$/i
|
326
|
+
return $1
|
327
|
+
else
|
328
|
+
return nil
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
|
333
|
+
### Constrain a value to alpha characters (a-z, case-insensitive and 0-9)
|
334
|
+
def match_alphanumeric( val )
|
335
|
+
if val =~ /^([a-z0-9]+)$/i
|
336
|
+
return $1
|
337
|
+
else
|
338
|
+
return nil
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
|
343
|
+
### Constrain a value to any printable characters
|
344
|
+
def match_printable( val )
|
345
|
+
if val =~ /^([[:print:][:space:]]{0,255})$/
|
346
|
+
return val
|
347
|
+
else
|
348
|
+
return nil
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
|
353
|
+
|
354
|
+
#
|
355
|
+
# RFC822 Email Address Regex
|
356
|
+
# --------------------------
|
357
|
+
#
|
358
|
+
# Originally written by Cal Henderson
|
359
|
+
# c.f. http://iamcal.com/publish/articles/php/parsing_email/
|
360
|
+
#
|
361
|
+
# Translated to Ruby by Tim Fletcher, with changes suggested by Dan Kubb.
|
362
|
+
#
|
363
|
+
# Licensed under a Creative Commons Attribution-ShareAlike 2.5 License
|
364
|
+
# http://creativecommons.org/licenses/by-sa/2.5/
|
365
|
+
#
|
366
|
+
RFC822_EMAIL_ADDRESS = begin
|
367
|
+
qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]'
|
368
|
+
dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]'
|
369
|
+
atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-' +
|
370
|
+
'\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+'
|
371
|
+
quoted_pair = '\\x5c[\\x00-\\x7f]'
|
372
|
+
domain_literal = "\\x5b(?:#{dtext}|#{quoted_pair})*\\x5d"
|
373
|
+
quoted_string = "\\x22(?:#{qtext}|#{quoted_pair})*\\x22"
|
374
|
+
domain_ref = atom
|
375
|
+
sub_domain = "(?:#{domain_ref}|#{domain_literal})"
|
376
|
+
word = "(?:#{atom}|#{quoted_string})"
|
377
|
+
domain = "#{sub_domain}(?:\\x2e#{sub_domain})*"
|
378
|
+
local_part = "#{word}(?:\\x2e#{word})*"
|
379
|
+
addr_spec = "#{local_part}\\x40#{domain}"
|
380
|
+
/\A#{addr_spec}\z/
|
381
|
+
end
|
382
|
+
|
383
|
+
### Override the parent class's definition to (not-sloppily) match email
|
384
|
+
### addresses.
|
385
|
+
def match_email( val )
|
386
|
+
match = RFC822_EMAIL_ADDRESS.match( val )
|
387
|
+
self.log.debug "Validating an email address %p: %p" %
|
388
|
+
[ val, match ]
|
389
|
+
return match ? match[0] : nil
|
390
|
+
end
|
391
|
+
|
392
|
+
|
393
|
+
RFC1738Hostname = begin
|
394
|
+
alphadigit = /[a-z0-9]/i
|
395
|
+
# toplabel = alpha | alpha *[ alphadigit | "-" ] alphadigit
|
396
|
+
toplabel = /[a-z]((#{alphadigit}|-)*#{alphadigit})?/i
|
397
|
+
# domainlabel = alphadigit | alphadigit *[ alphadigit | "-" ] alphadigit
|
398
|
+
domainlabel = /#{alphadigit}((#{alphadigit}|-)*#{alphadigit})?/i
|
399
|
+
# hostname = *[ domainlabel "." ] toplabel
|
400
|
+
hostname = /\A(#{domainlabel}\.)*#{toplabel}\z/
|
401
|
+
end
|
402
|
+
|
403
|
+
### Match valid hostnames according to the rules of the URL RFC.
|
404
|
+
def match_hostname( val )
|
405
|
+
match = RFC1738Hostname.match( val )
|
406
|
+
return match ? match[0] : nil
|
407
|
+
end
|
408
|
+
|
409
|
+
|
410
|
+
### Match valid URIs
|
411
|
+
def match_uri( val )
|
412
|
+
return URI.parse( val )
|
413
|
+
rescue URI::InvalidURIError => err
|
414
|
+
self.log.error "Error trying to parse URI %p: %s" % [ val, err.message ]
|
415
|
+
return nil
|
416
|
+
rescue NoMethodError
|
417
|
+
self.log.debug "Ignoring bug in URI#parse"
|
418
|
+
return nil
|
419
|
+
end
|
420
|
+
|
421
|
+
|
422
|
+
### Apply one or more +constraints+ to the field value/s corresponding to
|
423
|
+
### +key+.
|
424
|
+
def do_constraint( key, constraints )
|
425
|
+
constraints.each do |constraint|
|
426
|
+
case constraint
|
427
|
+
when String
|
428
|
+
apply_string_constraint( key, constraint )
|
429
|
+
when Hash
|
430
|
+
apply_hash_constraint( key, constraint )
|
431
|
+
when Proc
|
432
|
+
apply_proc_constraint( key, constraint )
|
433
|
+
when Regexp
|
434
|
+
apply_regexp_constraint( key, constraint )
|
435
|
+
else
|
436
|
+
raise "unknown constraint type %p" % [constraint]
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
|
442
|
+
### Applies a builtin constraint to form[key].
|
443
|
+
def apply_string_constraint( key, constraint )
|
444
|
+
# FIXME: multiple elements
|
445
|
+
rval = self.__send__( "match_#{constraint}", @form[key].to_s )
|
446
|
+
self.log.debug "Tried a string constraint: %p: %p" %
|
447
|
+
[ @form[key].to_s, rval ]
|
448
|
+
self.set_form_value( key, rval, constraint )
|
449
|
+
end
|
450
|
+
|
451
|
+
|
452
|
+
### Apply a constraint given as a Hash to the value/s corresponding to the
|
453
|
+
### specified +key+:
|
454
|
+
###
|
455
|
+
### constraint::
|
456
|
+
### A builtin constraint (as a Symbol; e.g., :email), a Regexp, or a Proc.
|
457
|
+
### name::
|
458
|
+
### A description of the constraint should it fail and be listed in #invalid.
|
459
|
+
### params::
|
460
|
+
### If +constraint+ is a Proc, this field should contain a list of other
|
461
|
+
### fields to send to the Proc.
|
462
|
+
def apply_hash_constraint( key, constraint )
|
463
|
+
action = constraint["constraint"]
|
464
|
+
|
465
|
+
rval = case action
|
466
|
+
when String
|
467
|
+
self.apply_string_constraint( key, action )
|
468
|
+
when Regexp
|
469
|
+
self.apply_regexp_constraint( key, action )
|
470
|
+
when Proc
|
471
|
+
if args = constraint["params"]
|
472
|
+
args.collect! {|field| @form[field] }
|
473
|
+
self.apply_proc_constraint( key, action, *args )
|
474
|
+
else
|
475
|
+
self.apply_proc_constraint( key, action )
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
# If the validation failed, and there's a name for this constraint, replace
|
480
|
+
# the name in @invalid_fields with the name
|
481
|
+
if !rval && constraint["name"]
|
482
|
+
@invalid_fields[key] = constraint["name"]
|
483
|
+
end
|
484
|
+
|
485
|
+
return rval
|
486
|
+
end
|
487
|
+
|
488
|
+
|
489
|
+
### Apply a constraint that was specified as a Proc to the value for the given
|
490
|
+
### +key+
|
491
|
+
def apply_proc_constraint( key, constraint, *params )
|
492
|
+
value = nil
|
493
|
+
|
494
|
+
unless params.empty?
|
495
|
+
value = constraint.call( *params )
|
496
|
+
else
|
497
|
+
value = constraint.call( @form[key] )
|
498
|
+
end
|
499
|
+
|
500
|
+
self.set_form_value( key, value, constraint )
|
501
|
+
end
|
502
|
+
|
503
|
+
|
504
|
+
### Applies regexp constraint to form[key]
|
505
|
+
def apply_regexp_constraint( key, constraint )
|
506
|
+
self.log.debug "Validating '%p' via regexp %p" % [@form[key], constraint]
|
507
|
+
|
508
|
+
if match = constraint.match( @form[key].to_s )
|
509
|
+
self.log.debug " matched %p" % [match[0]]
|
510
|
+
|
511
|
+
if match.captures.empty?
|
512
|
+
self.log.debug " no captures, using whole match: %p" % [match[0]]
|
513
|
+
self.set_form_value( key, match[0], constraint )
|
514
|
+
elsif match.captures.length == 1
|
515
|
+
self.log.debug " extracting one capture: %p" % [match.captures.first]
|
516
|
+
self.set_form_value( key, match.captures.first, constraint )
|
517
|
+
else
|
518
|
+
self.log.debug " extracting multiple captures: %p" % [match.captures]
|
519
|
+
self.set_form_value( key, match.captures, constraint )
|
520
|
+
end
|
521
|
+
else
|
522
|
+
self.set_form_value( key, nil, constraint )
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
|
527
|
+
### Set the form value for the given +key+. If +value+ is false, add it to
|
528
|
+
### the list of invalid fields with a description derived from the specified
|
529
|
+
### +constraint+.
|
530
|
+
def set_form_value( key, value, constraint )
|
531
|
+
key.untaint
|
532
|
+
|
533
|
+
if !value.nil?
|
534
|
+
self.log.debug "Setting form value for %p to %p (constraint was %p)" %
|
535
|
+
[ key, value, constraint ]
|
536
|
+
@form[key] = value
|
537
|
+
@form[key].untaint if self.untaint?( key )
|
538
|
+
return true
|
539
|
+
|
540
|
+
else
|
541
|
+
self.log.debug "Clearing form value for %p (constraint was %p)" %
|
542
|
+
[ key, constraint ]
|
543
|
+
@form.delete( key )
|
544
|
+
@invalid_fields ||= {}
|
545
|
+
@invalid_fields[ key ] ||= []
|
546
|
+
|
547
|
+
unless @invalid_fields[ key ].include?( constraint )
|
548
|
+
@invalid_fields[ key ].push( constraint )
|
549
|
+
end
|
550
|
+
return false
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
|
555
|
+
### Formvalidator hack:
|
556
|
+
### The formvalidator filters method has a bug where he assumes an array
|
557
|
+
### when it is in fact a string for multiple values (ie anytime you have a
|
558
|
+
### text-area with newlines in it).
|
559
|
+
def filters
|
560
|
+
@filters_array = Array(@profile[:filters]) unless(@filters_array)
|
561
|
+
@filters_array.each do |filter|
|
562
|
+
|
563
|
+
if respond_to?( "filter_#{filter}" )
|
564
|
+
@form.keys.each do |field|
|
565
|
+
# If a key has multiple elements, apply filter to each element
|
566
|
+
@field_array = Array( @form[field] )
|
567
|
+
|
568
|
+
if @field_array.length > 1
|
569
|
+
@field_array.each_index do |i|
|
570
|
+
elem = @field_array[i]
|
571
|
+
@field_array[i] = self.send("filter_#{filter}", elem)
|
572
|
+
end
|
573
|
+
else
|
574
|
+
if not @form[field].to_s.empty?
|
575
|
+
@form[field] = self.send("filter_#{filter}", @form[field].to_s)
|
576
|
+
end
|
577
|
+
end
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
@form
|
582
|
+
end
|
583
|
+
|
584
|
+
|
585
|
+
#######
|
586
|
+
private
|
587
|
+
#######
|
588
|
+
|
589
|
+
### Overridden to eliminate use of default #to_a (deprecated)
|
590
|
+
def strify_array( array )
|
591
|
+
array = [ array ] if !array.is_a?( Array )
|
592
|
+
array.map do |m|
|
593
|
+
m = (Array === m) ? strify_array(m) : m
|
594
|
+
m = (Hash === m) ? strify_hash(m) : m
|
595
|
+
Symbol === m ? m.to_s : m
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
|
600
|
+
### Build a deep hash out of the given parameter +value+
|
601
|
+
def build_deep_hash( value, hash, levels )
|
602
|
+
if levels.length == 0
|
603
|
+
value.untaint
|
604
|
+
elsif hash.nil?
|
605
|
+
{ levels.first => build_deep_hash(value, nil, levels[1..-1]) }
|
606
|
+
else
|
607
|
+
hash.update({ levels.first => build_deep_hash(value, hash[levels.first], levels[1..-1]) })
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
|
612
|
+
### Get the number of hash levels in the specified +key+
|
613
|
+
### Stolen from the CGIMethods class in Rails' action_controller.
|
614
|
+
PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/
|
615
|
+
def get_levels( key )
|
616
|
+
all, main, bracketed, trailing = PARAMS_HASH_RE.match( key ).to_a
|
617
|
+
if main.nil?
|
618
|
+
return []
|
619
|
+
elsif trailing
|
620
|
+
return [key.untaint]
|
621
|
+
elsif bracketed
|
622
|
+
return [main.untaint] + bracketed.slice(1...-1).split('][').collect {|k| k.untaint }
|
623
|
+
else
|
624
|
+
return [main.untaint]
|
625
|
+
end
|
626
|
+
end
|
627
|
+
|
628
|
+
end # class Arrow::FormValidator
|
629
|
+
|
630
|
+
|
631
|
+
|