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.
Files changed (198) hide show
  1. data/ChangeLog +1590 -0
  2. data/LICENSE +28 -0
  3. data/README +75 -0
  4. data/Rakefile +366 -0
  5. data/Rakefile.local +63 -0
  6. data/data/arrow/applets/TEMPLATE.rb.tpl +53 -0
  7. data/data/arrow/applets/args.rb +50 -0
  8. data/data/arrow/applets/config.rb +55 -0
  9. data/data/arrow/applets/error.rb +63 -0
  10. data/data/arrow/applets/files.rb +46 -0
  11. data/data/arrow/applets/inspect.rb +46 -0
  12. data/data/arrow/applets/nosuchapplet.rb +31 -0
  13. data/data/arrow/applets/status.rb +92 -0
  14. data/data/arrow/applets/test.rb +133 -0
  15. data/data/arrow/applets/tutorial/counter.rb +96 -0
  16. data/data/arrow/applets/tutorial/dingus.rb +67 -0
  17. data/data/arrow/applets/tutorial/hello.rb +34 -0
  18. data/data/arrow/applets/tutorial/hello2.rb +73 -0
  19. data/data/arrow/applets/tutorial/imgtext.rb +90 -0
  20. data/data/arrow/applets/tutorial/imgtext2.rb +286 -0
  21. data/data/arrow/applets/tutorial/index.rb +36 -0
  22. data/data/arrow/applets/tutorial/logo.rb +98 -0
  23. data/data/arrow/applets/tutorial/memcache.rb +61 -0
  24. data/data/arrow/applets/tutorial/missing.rb +37 -0
  25. data/data/arrow/applets/tutorial/protected.rb +100 -0
  26. data/data/arrow/applets/tutorial/redirector.rb +52 -0
  27. data/data/arrow/applets/tutorial/rndimages.rb +159 -0
  28. data/data/arrow/applets/tutorial/sharenotes.rb +83 -0
  29. data/data/arrow/applets/tutorial/subclassed-hello.rb +32 -0
  30. data/data/arrow/applets/tutorial/superhello.rb +72 -0
  31. data/data/arrow/applets/tutorial/timeclock.rb +78 -0
  32. data/data/arrow/applets/view-applet.rb +123 -0
  33. data/data/arrow/applets/view-template.rb +85 -0
  34. data/data/arrow/applets/wiki.rb +274 -0
  35. data/data/arrow/templates/TEMPLATE.tmpl.tpl +36 -0
  36. data/data/arrow/templates/applet-status.tmpl +153 -0
  37. data/data/arrow/templates/args-display.tmpl +120 -0
  38. data/data/arrow/templates/config/display-table.tmpl +36 -0
  39. data/data/arrow/templates/config/display.tmpl +36 -0
  40. data/data/arrow/templates/counter-deleted.tmpl +33 -0
  41. data/data/arrow/templates/counter.tmpl +59 -0
  42. data/data/arrow/templates/dingus.tmpl +55 -0
  43. data/data/arrow/templates/enumtable.tmpl +8 -0
  44. data/data/arrow/templates/error-display.tmpl +92 -0
  45. data/data/arrow/templates/filemap.tmpl +89 -0
  46. data/data/arrow/templates/hello-world-src.tmpl +34 -0
  47. data/data/arrow/templates/hello-world.tmpl +60 -0
  48. data/data/arrow/templates/imgtext/fontlist.tmpl +46 -0
  49. data/data/arrow/templates/imgtext/form.tmpl +70 -0
  50. data/data/arrow/templates/imgtext/reload-error.tmpl +40 -0
  51. data/data/arrow/templates/imgtext/reload.tmpl +55 -0
  52. data/data/arrow/templates/inspect/display.tmpl +80 -0
  53. data/data/arrow/templates/loginform.tmpl +64 -0
  54. data/data/arrow/templates/logout.tmpl +32 -0
  55. data/data/arrow/templates/memcache/display.tmpl +41 -0
  56. data/data/arrow/templates/navbar.incl +27 -0
  57. data/data/arrow/templates/nosuchapplet.tmpl +32 -0
  58. data/data/arrow/templates/printsource.tmpl +35 -0
  59. data/data/arrow/templates/protected.tmpl +36 -0
  60. data/data/arrow/templates/rndimages.tmpl +38 -0
  61. data/data/arrow/templates/service-response.tmpl +13 -0
  62. data/data/arrow/templates/sharenotes/display.tmpl +38 -0
  63. data/data/arrow/templates/status.tmpl +120 -0
  64. data/data/arrow/templates/templateviewer.tmpl +43 -0
  65. data/data/arrow/templates/test/harness.tmpl +57 -0
  66. data/data/arrow/templates/test/list.tmpl +48 -0
  67. data/data/arrow/templates/test/problem.tmpl +42 -0
  68. data/data/arrow/templates/tutorial/index.tmpl +37 -0
  69. data/data/arrow/templates/tutorial/missingapplet.tmpl +29 -0
  70. data/data/arrow/templates/view-applet-nosuch.tmpl +32 -0
  71. data/data/arrow/templates/view-applet.tmpl +40 -0
  72. data/data/arrow/templates/view-template.tmpl +83 -0
  73. data/data/arrow/templates/wiki/formerror.tmpl +47 -0
  74. data/data/arrow/templates/wiki/markup_help.incl +6 -0
  75. data/data/arrow/templates/wiki/new.tmpl +56 -0
  76. data/data/arrow/templates/wiki/new_system.tmpl +122 -0
  77. data/data/arrow/templates/wiki/sectionlist.tmpl +43 -0
  78. data/data/arrow/templates/wiki/show.tmpl +34 -0
  79. data/docs/manual/layouts/default.page +43 -0
  80. data/docs/manual/lib/api-filter.rb +81 -0
  81. data/docs/manual/lib/editorial-filter.rb +64 -0
  82. data/docs/manual/lib/examples-filter.rb +244 -0
  83. data/docs/manual/lib/links-filter.rb +117 -0
  84. data/lib/apache/fakerequest.rb +448 -0
  85. data/lib/apache/logger.rb +33 -0
  86. data/lib/arrow.rb +51 -0
  87. data/lib/arrow/acceptparam.rb +207 -0
  88. data/lib/arrow/applet.rb +725 -0
  89. data/lib/arrow/appletmixins.rb +218 -0
  90. data/lib/arrow/appletregistry.rb +590 -0
  91. data/lib/arrow/applettestcase.rb +503 -0
  92. data/lib/arrow/broker.rb +255 -0
  93. data/lib/arrow/cache.rb +176 -0
  94. data/lib/arrow/config-loaders/yaml.rb +75 -0
  95. data/lib/arrow/config.rb +615 -0
  96. data/lib/arrow/constants.rb +24 -0
  97. data/lib/arrow/cookie.rb +359 -0
  98. data/lib/arrow/cookieset.rb +108 -0
  99. data/lib/arrow/dispatcher.rb +368 -0
  100. data/lib/arrow/dispatcherloader.rb +50 -0
  101. data/lib/arrow/exceptions.rb +61 -0
  102. data/lib/arrow/fallbackhandler.rb +48 -0
  103. data/lib/arrow/formvalidator.rb +631 -0
  104. data/lib/arrow/htmltokenizer.rb +343 -0
  105. data/lib/arrow/logger.rb +488 -0
  106. data/lib/arrow/logger/apacheoutputter.rb +69 -0
  107. data/lib/arrow/logger/arrayoutputter.rb +63 -0
  108. data/lib/arrow/logger/coloroutputter.rb +111 -0
  109. data/lib/arrow/logger/fileoutputter.rb +96 -0
  110. data/lib/arrow/logger/htmloutputter.rb +54 -0
  111. data/lib/arrow/logger/outputter.rb +123 -0
  112. data/lib/arrow/mixins.rb +425 -0
  113. data/lib/arrow/monkeypatches.rb +94 -0
  114. data/lib/arrow/object.rb +117 -0
  115. data/lib/arrow/path.rb +196 -0
  116. data/lib/arrow/service.rb +447 -0
  117. data/lib/arrow/session.rb +289 -0
  118. data/lib/arrow/session/dbstore.rb +100 -0
  119. data/lib/arrow/session/filelock.rb +160 -0
  120. data/lib/arrow/session/filestore.rb +132 -0
  121. data/lib/arrow/session/id.rb +98 -0
  122. data/lib/arrow/session/lock.rb +253 -0
  123. data/lib/arrow/session/md5id.rb +42 -0
  124. data/lib/arrow/session/nulllock.rb +42 -0
  125. data/lib/arrow/session/posixlock.rb +166 -0
  126. data/lib/arrow/session/sha1id.rb +54 -0
  127. data/lib/arrow/session/store.rb +366 -0
  128. data/lib/arrow/session/usertrackid.rb +52 -0
  129. data/lib/arrow/spechelpers.rb +73 -0
  130. data/lib/arrow/template.rb +713 -0
  131. data/lib/arrow/template/attr.rb +31 -0
  132. data/lib/arrow/template/call.rb +31 -0
  133. data/lib/arrow/template/comment.rb +33 -0
  134. data/lib/arrow/template/container.rb +118 -0
  135. data/lib/arrow/template/else.rb +41 -0
  136. data/lib/arrow/template/elsif.rb +44 -0
  137. data/lib/arrow/template/escape.rb +53 -0
  138. data/lib/arrow/template/export.rb +87 -0
  139. data/lib/arrow/template/for.rb +145 -0
  140. data/lib/arrow/template/if.rb +78 -0
  141. data/lib/arrow/template/import.rb +119 -0
  142. data/lib/arrow/template/include.rb +206 -0
  143. data/lib/arrow/template/iterator.rb +208 -0
  144. data/lib/arrow/template/nodes.rb +734 -0
  145. data/lib/arrow/template/parser.rb +571 -0
  146. data/lib/arrow/template/prettyprint.rb +53 -0
  147. data/lib/arrow/template/render.rb +191 -0
  148. data/lib/arrow/template/selectlist.rb +94 -0
  149. data/lib/arrow/template/set.rb +87 -0
  150. data/lib/arrow/template/timedelta.rb +81 -0
  151. data/lib/arrow/template/unless.rb +78 -0
  152. data/lib/arrow/template/urlencode.rb +51 -0
  153. data/lib/arrow/template/yield.rb +139 -0
  154. data/lib/arrow/templatefactory.rb +125 -0
  155. data/lib/arrow/testcase.rb +567 -0
  156. data/lib/arrow/transaction.rb +608 -0
  157. data/rake/191_compat.rb +26 -0
  158. data/rake/dependencies.rb +76 -0
  159. data/rake/documentation.rb +114 -0
  160. data/rake/helpers.rb +502 -0
  161. data/rake/hg.rb +282 -0
  162. data/rake/manual.rb +787 -0
  163. data/rake/packaging.rb +129 -0
  164. data/rake/publishing.rb +278 -0
  165. data/rake/style.rb +62 -0
  166. data/rake/svn.rb +668 -0
  167. data/rake/testing.rb +187 -0
  168. data/rake/verifytask.rb +64 -0
  169. data/spec/arrow/acceptparam_spec.rb +157 -0
  170. data/spec/arrow/applet_spec.rb +575 -0
  171. data/spec/arrow/appletmixins_spec.rb +409 -0
  172. data/spec/arrow/appletregistry_spec.rb +294 -0
  173. data/spec/arrow/broker_spec.rb +153 -0
  174. data/spec/arrow/config_spec.rb +224 -0
  175. data/spec/arrow/cookieset_spec.rb +164 -0
  176. data/spec/arrow/dispatcher_spec.rb +137 -0
  177. data/spec/arrow/dispatcherloader_spec.rb +65 -0
  178. data/spec/arrow/formvalidator_spec.rb +781 -0
  179. data/spec/arrow/logger_spec.rb +346 -0
  180. data/spec/arrow/mixins_spec.rb +120 -0
  181. data/spec/arrow/service_spec.rb +645 -0
  182. data/spec/arrow/session_spec.rb +121 -0
  183. data/spec/arrow/template/iterator_spec.rb +222 -0
  184. data/spec/arrow/templatefactory_spec.rb +185 -0
  185. data/spec/arrow/transaction_spec.rb +319 -0
  186. data/spec/arrow_spec.rb +37 -0
  187. data/spec/lib/appletmatchers.rb +281 -0
  188. data/spec/lib/constants.rb +77 -0
  189. data/spec/lib/helpers.rb +41 -0
  190. data/spec/lib/matchers.rb +44 -0
  191. data/tests/cookie.tests.rb +310 -0
  192. data/tests/path.tests.rb +157 -0
  193. data/tests/session.tests.rb +111 -0
  194. data/tests/session_id.tests.rb +82 -0
  195. data/tests/session_lock.tests.rb +191 -0
  196. data/tests/session_store.tests.rb +53 -0
  197. data/tests/template.tests.rb +1360 -0
  198. 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
+