form_input 1.3.0 → 1.4.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c46667c81eb96ef6deeaf13501e06f6bc328b862
4
- data.tar.gz: e6dc22529b1fdfead38c01c5bd2d6f5b2e1fc52a
2
+ SHA256:
3
+ metadata.gz: f5f2db9b4c0442f601294eda78d17eae6fa07658d746123b34904f3b436305ec
4
+ data.tar.gz: 3895b0ddd7f21befbb375d337a485af8c533641faa1171c1f643b14efa306fdf
5
5
  SHA512:
6
- metadata.gz: 178de263faf14e4621298bfdee13025cd0852020178448f9370d4c27904c9a9a5bbad2c8fb250a55ee10051c370716f23b42eadeec89a4c157055aa12b444bf7
7
- data.tar.gz: e62589e13773fd867abbd1bec66a66dad851bda8214968820348ac3566e612ffe48e702a93d1928b5f505c8b53b7d06591923a67969b19e1989979ec3bf2f275
6
+ metadata.gz: 6864fff3b9a98fab84caba8c5768fd7a4f56c43792e0b540b9d3a8187c5eea9290cce2d0807512c5e9839515b3b48999f1f3e78d560af81765950215c483c981
7
+ data.tar.gz: 1dd327e0efa4d7c940023a4eae1be1cfd357def6dfd4f432ba9f960e1f42513f5aaeb9ad13afebb6cad2a27625e15dd1abca3c597a9b205fb2bdfb28b250c9ca
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ = 1.4.0
2
+
3
+ * Dependencies were updated to support latest gems, in particular both Rack 2.x and 3.x are now supported.
4
+
5
+ * The inputs containing characters from Unicode Cf (Format) and Co (Private Use) categories are rejected as was intended.
6
+
7
+ * More robust handling of inputs with invalid encoding:
8
+ * Strings returned by `form_value` are now scrubbed so they don't cause errors when used in templates.
9
+ * The hash keys with invalid encoding are now handled properly, too.
10
+
1
11
  = 1.3.0
2
12
 
3
13
  * Further improvements for JSON payloads:
data/README.md CHANGED
@@ -50,7 +50,7 @@ Using them in your templates is as simple as this:
50
50
  .panel-heading
51
51
  = @title = "Contact Form"
52
52
  .panel-body
53
- form *form_attrs
53
+ form method='post' action=request.path
54
54
  fieldset
55
55
  == snippet :form_panel, params: @form.params
56
56
  button.btn.btn-default type='submit' Send
@@ -1037,7 +1037,7 @@ The `validate!` method on the other hand always invokes the validation,
1037
1037
  wiping any previously reported errors first.
1038
1038
 
1039
1039
  In either case any errors collected will remain stored
1040
- until you change any of the parameter values with `set`, `clear`, or `[]=` methods,
1040
+ until you change any of the parameter values with `set`, `unset`, `clear`, or `[]=` methods,
1041
1041
  or you explicitly call `validate!`.
1042
1042
  Copies created with `dup` (but not `clone`), `only`, and `except` methods
1043
1043
  also have any errors reported before cleared.
@@ -1399,6 +1399,42 @@ Even the `nil` values are included for parameters which were explicitly set to `
1399
1399
  OptionalInput.from_data( hash: {} ).to_data # { hash: {} }
1400
1400
  ```
1401
1401
 
1402
+ With regards to `nil` values, you may want to differentiate
1403
+ between which parameters may be set to `nil` and which not
1404
+ (in addition to parameters being required or not).
1405
+ In such case, you can extend your JSON processing class wrapping `FormInput` like this:
1406
+
1407
+ ``` ruby
1408
+ # Like param, but the parameter may be nil (and other parameters now can't).
1409
+ def self.param?( name, *args, &block )
1410
+ param( name, *args, allow_nil: true, &block )
1411
+ end
1412
+
1413
+ # Like normal validate, but makes sure the parameters are not nil unless allowed.
1414
+ def validate
1415
+ super
1416
+ set_params.each do |p|
1417
+ unless p[ :allow_nil ]
1418
+ p.report( "%p is nil" ) if p.value.nil?
1419
+ end
1420
+ end
1421
+ self
1422
+ end
1423
+ ```
1424
+
1425
+ This allows you to explicitely distinguish the three most commonly used cases with ease,
1426
+ especially when combined with further parameter types and checks:
1427
+
1428
+ ``` ruby
1429
+ param :a, INTEGER_ARGS # When set, this must be an integer.
1430
+ param? :b, INTEGER_ARGS # When set, this must be nil or an integer.
1431
+ param! :c, INTEGER_ARGS # This must be set to an integer.
1432
+
1433
+ param :s # When set, this must be a string, albeit perhaps empty.
1434
+ param? :t # When set, this must be nil or a string, including empty.
1435
+ param! :n # This must be set to a non-empty string.
1436
+ ```
1437
+
1402
1438
  The whole JSON processing may in the end look something like this:
1403
1439
 
1404
1440
  ``` ruby
@@ -1467,6 +1503,19 @@ and keys are passed to `form_name` to create the actual name:
1467
1503
  input type=p.type name=p.form_name( key ) value=value
1468
1504
  ```
1469
1505
 
1506
+ Note that for your convenience,
1507
+ any strings returned by `form_value` are scrubbed of characters in invalid encoding,
1508
+ should there be any,
1509
+ which makes them guaranteed to coalesce with the form template without triggering encoding errors.
1510
+ Under normal circumstances you would never encounter such invalid inputs,
1511
+ but sometimes hackers use them to try to break the applications on purpose.
1512
+ The scrubbing thus takes care that you don't have to deal with this in templates specially if it ever happens.
1513
+ However,
1514
+ if you would for some reason ever need the `form_value` without the scrubbing applied,
1515
+ note that you can use the `url_value` method
1516
+ (used internally by `url_params` and `url_query`, see [URL Helpers](#url-helpers))
1517
+ instead.
1518
+
1470
1519
  For parameters which require additional data,
1471
1520
  like select, multi-select, or multi-checkbox parameters,
1472
1521
  you can ask for the data using the `data` method.
@@ -1721,11 +1770,8 @@ For [Sinatra], the helper may look like this:
1721
1770
 
1722
1771
  ``` ruby
1723
1772
  # Get hash with default form attributes, optionally overriding them as needed.
1724
- def form_attrs( *args )
1725
- opts = args.last.is_a?( Hash ) ? args.pop : {}
1726
- url = args.shift.to_s unless args.empty?
1727
- fail( ArgumentError, "Invalid arguments #{args.inspect}" ) unless args.empty?
1728
- { action: url || request.path, method: :post }.merge( opts )
1773
+ def form_attrs( url = request.path, **opts )
1774
+ { action: url.to_s, method: :post }.merge( opts )
1729
1775
  end
1730
1776
  ```
1731
1777
 
data/form_input.gemspec CHANGED
@@ -23,11 +23,13 @@ EOT
23
23
 
24
24
  s.files = %w[ LICENSE README.md CHANGELOG.md Rakefile .yardopts form_input.gemspec ] + Dir[ '{lib,test,example}/**/*.{rb,yml,txt,slim}' ]
25
25
 
26
- s.required_ruby_version = '>= 2.0.0'
27
- s.add_runtime_dependency 'rack', '>= 1.5', '< 3.0'
26
+ s.required_ruby_version = '>= 2.1.0'
27
+ s.add_runtime_dependency 'rack', '>= 1.5', '< 4.0'
28
+ s.add_development_dependency 'rake', '~> 13.0'
28
29
  s.add_development_dependency 'bacon', '~> 1.2'
29
- s.add_development_dependency 'rack-test', '~> 0.6'
30
- s.add_development_dependency 'r18n-core', '~> 2.0'
30
+ s.add_development_dependency 'simplecov', '~> 0.21'
31
+ s.add_development_dependency 'rack-test', '>= 1.0', '< 3.0'
32
+ s.add_development_dependency 'r18n-core', '~> 5.0'
31
33
  end
32
34
 
33
35
  # EOF #
@@ -8,9 +8,20 @@ class FormInput
8
8
  # Default size limit applied to all input.
9
9
  DEFAULT_SIZE_LIMIT = 255
10
10
 
11
- # Default input filter applied to all input.
11
+ # Default input filter applied to all input (unless replaced by user's filter).
12
12
  DEFAULT_FILTER = ->{ gsub( /\s+/, ' ' ).strip }
13
13
 
14
+ # Default match applied to all input (in addition to user's match(es)).
15
+ # Note that the Graph property, despite being defined as "Non-blank characters",
16
+ # currently includes Cf (Other: Format) and Co (Other: Private Use) categories,
17
+ # which include stuff like BOM or BIDI formatting and other invisible characters,
18
+ # which you may or may not want. To protect the innocent and prevent nasty surprises,
19
+ # DEFAULT_REJECT was introduced to avoid these by default, but make it possible to override.
20
+ DEFAULT_MATCH = /\A(\p{Graph}|[ \t\r\n])*\z/u
21
+
22
+ # Default reject applied to all input (in addition to user's reject(s)).
23
+ DEFAULT_REJECT = /\p{Cf}|\p{Co}/u
24
+
14
25
  # Minimum hash key value we allow by default.
15
26
  DEFAULT_MIN_KEY = 0
16
27
 
@@ -49,6 +60,9 @@ class FormInput
49
60
  match_msg: "%p like this is not valid",
50
61
  }
51
62
 
63
+ # Character used as default replacement for invalid characters when formatting values.
64
+ DEFAULT_REPLACEMENT_CHARACTER = '?'
65
+
52
66
  # Parameter options which can be merged together into an array when multiple option hashes are merged.
53
67
  MERGEABLE_OPTIONS = [ :check, :test ]
54
68
 
@@ -92,8 +106,17 @@ class FormInput
92
106
  form ? form[ name ] : nil
93
107
  end
94
108
 
109
+ # Make sure given string is in our default encoding and contains only valid characters.
110
+ def cleanup_string( string, replacement )
111
+ unless string.valid_encoding? && ( string.encoding == DEFAULT_ENCODING || string.ascii_only? )
112
+ string = string.dup.force_encoding( DEFAULT_ENCODING ).scrub( replacement )
113
+ end
114
+ string
115
+ end
116
+
95
117
  # Format given value for form/URL output, applying the formatting filter as necessary.
96
- def format_value( value )
118
+ def format_value( value, replacement = DEFAULT_REPLACEMENT_CHARACTER )
119
+ value = cleanup_string( value, replacement ) if replacement and value.is_a?( String )
97
120
  if format.nil? or value.nil? or ( value.is_a?( String ) and type = self[ :class ] and type != String )
98
121
  value.to_s
99
122
  else
@@ -102,16 +125,26 @@ class FormInput
102
125
  end
103
126
 
104
127
  # Get value of this parameter for use in form/URL, with all scalar values converted to strings.
105
- def form_value
128
+ def formatted_value( replacement = DEFAULT_REPLACEMENT_CHARACTER )
106
129
  if array?
107
- [ *value ].map{ |x| format_value( x ) }
130
+ [ *value ].map{ |x| format_value( x, replacement ) }
108
131
  elsif hash?
109
- Hash[ [ *value ].map{ |k, v| [ k.to_s, format_value( v ) ] } ]
132
+ Hash[ [ *value ].map{ |k, v| [ replacement ? cleanup_string( k.to_s, replacement ) : k.to_s, format_value( v, replacement ) ] } ]
110
133
  else
111
- format_value( value )
134
+ format_value( value, replacement )
112
135
  end
113
136
  end
114
137
 
138
+ # Get value of this parameter for use in form, with all scalar values converted to strings.
139
+ def form_value
140
+ formatted_value
141
+ end
142
+
143
+ # Get value of this parameter for use in URL, with all scalar values converted to strings.
144
+ def url_value
145
+ formatted_value( nil )
146
+ end
147
+
115
148
  # Test if given parameter has value of correct type.
116
149
  def correct?
117
150
  case v = value
@@ -452,7 +485,7 @@ class FormInput
452
485
  # If there is a key pattern specified, make sure the key matches.
453
486
 
454
487
  if patterns = self[ :match_key ]
455
- unless [ *patterns ].all?{ |x| value.to_s =~ x }
488
+ unless value.to_s.valid_encoding? and [ *patterns ].all?{ |x| value.to_s =~ x }
456
489
  report( :match_key )
457
490
  return
458
491
  end
@@ -563,7 +596,7 @@ class FormInput
563
596
  return
564
597
  end
565
598
 
566
- unless value =~ /\A(\p{Graph}|[ \t\r\n])*\z/u
599
+ unless value =~ DEFAULT_MATCH && value !~ DEFAULT_REJECT
567
600
  report( :invalid_characters )
568
601
  return
569
602
  end
@@ -1113,15 +1146,32 @@ class FormInput
1113
1146
  # Get hash of all non-empty parameters for use in URL.
1114
1147
  def url_params
1115
1148
  result = {}
1116
- filled_params.each{ |x| result[ x.code ] = x.form_value }
1149
+ filled_params.each{ |x| result[ x.code ] = x.url_value }
1117
1150
  result
1118
1151
  end
1119
1152
  alias url_parameters url_params
1120
1153
  alias to_params url_params
1121
1154
 
1155
+ # Escape given string for use in URL query.
1156
+ def url_escape( string )
1157
+ URI.encode_www_form_component( string )
1158
+ end
1159
+
1160
+ # Build URL query from given URL parameters.
1161
+ def build_url_query( value, prefix = nil )
1162
+ case value
1163
+ when Hash
1164
+ value.map{ |k, v| k = url_escape( k ) ; build_url_query( v, prefix ? "#{prefix}[#{k}]" : k ) }.join( '&' )
1165
+ when Array
1166
+ value.map{ |v| build_url_query( v, "#{prefix}[]" ) }.join( '&' )
1167
+ else
1168
+ "#{prefix}=#{url_escape( value )}"
1169
+ end
1170
+ end
1171
+
1122
1172
  # Create string containing URL query from all current non-empty parameters.
1123
1173
  def url_query
1124
- Rack::Utils.build_nested_query( url_params )
1174
+ build_url_query( url_params )
1125
1175
  end
1126
1176
 
1127
1177
  # Extend given URL with query created from all current non-empty parameters.
@@ -3,7 +3,7 @@
3
3
  class FormInput
4
4
  module Version
5
5
  MAJOR = 1
6
- MINOR = 3
6
+ MINOR = 4
7
7
  PATCH = 0
8
8
  STRING = [ MAJOR, MINOR, PATCH ].join( '.' ).freeze
9
9
  end
data/test/helper.rb CHANGED
@@ -14,7 +14,9 @@ end unless jruby?
14
14
 
15
15
  if ENV[ 'COVERAGE' ]
16
16
  require 'simplecov'
17
- SimpleCov.start
17
+ SimpleCov.start do
18
+ add_filter 'bundler'
19
+ end
18
20
  end
19
21
 
20
22
  # EOF #
data/test/test_core.rb CHANGED
@@ -115,7 +115,15 @@ describe FormInput do
115
115
  [ 'q is required', q: "\u{0000}" ], # Because strip strips \0 as well.
116
116
  [ 'q may not contain invalid characters', q: "a\u{0000}b" ],
117
117
  [ 'q may not contain invalid characters', q: "\u{0001}" ],
118
- [ 'q may not contain invalid characters', q: "\u{2029}" ],
118
+ [ 'q may not contain invalid characters', q: "\u{2029}" ], # NBSP
119
+ [ 'q may not contain invalid characters', q: "\u{00AD}" ], # Soft-hyphen
120
+ [ 'q may not contain invalid characters', q: "\u{FEFF}" ], # BOM, ZWNBSP
121
+ [ 'q may not contain invalid characters', q: "\u{E000}" ], # Private use
122
+ [ 'q may not contain invalid characters', q: "\u{F8FF}" ], # Private use
123
+ [ 'q may not contain invalid characters', q: "\u{F0000}" ], # Private use plane 15
124
+ [ 'q may not contain invalid characters', q: "\u{FFFFD}" ], # Private use plane 15
125
+ [ 'q may not contain invalid characters', q: "\u{100000}" ], # Private use plane 16
126
+ [ 'q may not contain invalid characters', q: "\u{10FFFD}" ], # Private use plane 16
119
127
  [ 'email address like this is not valid', email: 'abc' ],
120
128
  [ 'email address may have at most 255 characters', email: 'a@' + 'a' * 254 ],
121
129
  [ 'email address may have at most 255 bytes', email: 'á@' + 'a' * 253 ],
@@ -1209,12 +1217,17 @@ describe FormInput do
1209
1217
 
1210
1218
  should 'handle invalid encoding gracefully' do
1211
1219
  s = 255.chr.force_encoding( 'UTF-8' )
1220
+ b = s.dup.force_encoding( 'BINARY' )
1212
1221
 
1213
1222
  f = TestForm.new( query: s )
1214
1223
  ->{ f.validate }.should.not.raise
1215
1224
  f.should.not.be.valid
1216
1225
  f.error_messages.should == [ "q must use valid encoding" ]
1217
- f.param( :query ).should.not.be.blank
1226
+ p = f.param( :query )
1227
+ p.should.not.be.blank
1228
+ p.should.be.invalid
1229
+ p.form_value.should == '?'
1230
+ p.url_value.should == s
1218
1231
  f.to_hash.should == { query: s }
1219
1232
  f.to_data.should == { q: s }
1220
1233
  f.url_params.should == { q: s }
@@ -1224,11 +1237,46 @@ describe FormInput do
1224
1237
  ->{ f.validate }.should.not.raise
1225
1238
  f.should.not.be.valid
1226
1239
  f.error_messages.should == [ "q must use valid encoding" ]
1227
- f.param( :query ).should.not.be.blank
1228
- f.to_hash.should == { query: s.dup.force_encoding( 'BINARY' ) }
1229
- f.to_data.should == { q: s.dup.force_encoding( 'BINARY' ) }
1230
- f.url_params.should == { q: s.dup.force_encoding( 'BINARY' ) }
1240
+ p = f.param( :query )
1241
+ p.should.not.be.blank
1242
+ p.should.be.invalid
1243
+ p.form_value.should == '?'
1244
+ p.url_value.should == b
1245
+ f.to_hash.should == { query: b }
1246
+ f.to_data.should == { q: b }
1247
+ f.url_params.should == { q: b }
1231
1248
  f.url_query.should == "q=%FF"
1249
+
1250
+ c = Class.new( FormInput )
1251
+ c.hash :hsh, :h, match_key: /\A\w+\z/
1252
+
1253
+ f = c.new( hsh: { s => 1, 2 => s } )
1254
+ ->{ f.validate }.should.not.raise
1255
+ f.should.not.be.valid
1256
+ f.error_messages.should == [ "h contain invalid key" ]
1257
+ p = f.param( :hsh )
1258
+ p.should.not.be.blank
1259
+ p.should.be.invalid
1260
+ p.form_value.should == { '?' => '1', '2' => '?' }
1261
+ p.url_value.should == { s => '1', '2' => s }
1262
+ f.to_hash.should == { hsh: { s => 1, 2 => s } }
1263
+ f.to_data.should == { h: { s => 1, 2 => s } }
1264
+ f.url_params.should == { h: { s => '1', '2' => s } }
1265
+ f.url_query.should == "h[%FF]=1&h[2]=%FF"
1266
+
1267
+ f = c.new( request( "?h[%ff]=1&h[2]=%ff" ) )
1268
+ ->{ f.validate }.should.not.raise
1269
+ f.should.not.be.valid
1270
+ f.error_messages.should == [ "h contain invalid key" ]
1271
+ p = f.param( :hsh )
1272
+ p.should.not.be.blank
1273
+ p.should.be.invalid
1274
+ p.form_value.should == { '?' => '1', '2' => '?' }
1275
+ p.url_value.should == { s => '1', '2' => b }
1276
+ f.to_hash.should == { hsh: { s => '1', 2 => b } }
1277
+ f.to_data.should == { h: { s => '1', 2 => b } }
1278
+ f.url_params.should == { h: { s => '1', '2' => b } }
1279
+ f.url_query.should == "h[%FF]=1&h[2]=%FF"
1232
1280
  end
1233
1281
 
1234
1282
  should 'reject unexpected values in request input' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: form_input
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Rak
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-31 00:00:00.000000000 Z
11
+ date: 2025-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '1.5'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '3.0'
22
+ version: '4.0'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,21 @@ dependencies:
29
29
  version: '1.5'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '3.0'
32
+ version: '4.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: rake
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '13.0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
33
47
  - !ruby/object:Gem::Dependency
34
48
  name: bacon
35
49
  requirement: !ruby/object:Gem::Requirement
@@ -45,33 +59,53 @@ dependencies:
45
59
  - !ruby/object:Gem::Version
46
60
  version: '1.2'
47
61
  - !ruby/object:Gem::Dependency
48
- name: rack-test
62
+ name: simplecov
49
63
  requirement: !ruby/object:Gem::Requirement
50
64
  requirements:
51
65
  - - "~>"
52
66
  - !ruby/object:Gem::Version
53
- version: '0.6'
67
+ version: '0.21'
54
68
  type: :development
55
69
  prerelease: false
56
70
  version_requirements: !ruby/object:Gem::Requirement
57
71
  requirements:
58
72
  - - "~>"
59
73
  - !ruby/object:Gem::Version
60
- version: '0.6'
74
+ version: '0.21'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rack-test
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '1.0'
82
+ - - "<"
83
+ - !ruby/object:Gem::Version
84
+ version: '3.0'
85
+ type: :development
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '1.0'
92
+ - - "<"
93
+ - !ruby/object:Gem::Version
94
+ version: '3.0'
61
95
  - !ruby/object:Gem::Dependency
62
96
  name: r18n-core
63
97
  requirement: !ruby/object:Gem::Requirement
64
98
  requirements:
65
99
  - - "~>"
66
100
  - !ruby/object:Gem::Version
67
- version: '2.0'
101
+ version: '5.0'
68
102
  type: :development
69
103
  prerelease: false
70
104
  version_requirements: !ruby/object:Gem::Requirement
71
105
  requirements:
72
106
  - - "~>"
73
107
  - !ruby/object:Gem::Version
74
- version: '2.0'
108
+ version: '5.0'
75
109
  description: |
76
110
  This gem allows you to describe your forms using a simple DSL
77
111
  and then takes care of sanitizing, transforming, and validating the input for you,
@@ -150,18 +184,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
150
184
  requirements:
151
185
  - - ">="
152
186
  - !ruby/object:Gem::Version
153
- version: 2.0.0
187
+ version: 2.1.0
154
188
  required_rubygems_version: !ruby/object:Gem::Requirement
155
189
  requirements:
156
190
  - - ">="
157
191
  - !ruby/object:Gem::Version
158
192
  version: '0'
159
193
  requirements: []
160
- rubyforge_project:
161
- rubygems_version: 2.4.5.1
194
+ rubygems_version: 3.0.3.1
162
195
  signing_key:
163
196
  specification_version: 4
164
197
  summary: Form helper which sanitizes, transforms, validates and encapsulates web request
165
198
  input.
166
199
  test_files: []
167
- has_rdoc: