arrow 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
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
+