owasp-esapi-ruby 0.30.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/AUTHORS +5 -0
- data/ChangeLog +69 -0
- data/ISSUES +0 -0
- data/LICENSE +24 -0
- data/README +51 -0
- data/Rakefile +63 -0
- data/VERSION +1 -0
- data/lib/codec/base_codec.rb +99 -0
- data/lib/codec/css_codec.rb +101 -0
- data/lib/codec/encoder.rb +330 -0
- data/lib/codec/html_codec.rb +424 -0
- data/lib/codec/javascript_codec.rb +119 -0
- data/lib/codec/mysql_codec.rb +131 -0
- data/lib/codec/oracle_codec.rb +46 -0
- data/lib/codec/os_codec.rb +78 -0
- data/lib/codec/percent_codec.rb +53 -0
- data/lib/codec/pushable_string.rb +114 -0
- data/lib/codec/vbscript_codec.rb +64 -0
- data/lib/codec/xml_codec.rb +173 -0
- data/lib/esapi.rb +68 -0
- data/lib/exceptions.rb +37 -0
- data/lib/executor.rb +20 -0
- data/lib/owasp-esapi-ruby.rb +13 -0
- data/lib/sanitizer/xss.rb +59 -0
- data/lib/validator/base_rule.rb +90 -0
- data/lib/validator/date_rule.rb +92 -0
- data/lib/validator/email.rb +29 -0
- data/lib/validator/float_rule.rb +76 -0
- data/lib/validator/generic_validator.rb +26 -0
- data/lib/validator/integer_rule.rb +61 -0
- data/lib/validator/string_rule.rb +146 -0
- data/lib/validator/validator_error_list.rb +48 -0
- data/lib/validator/zipcode.rb +27 -0
- data/spec/codec/css_codec_spec.rb +61 -0
- data/spec/codec/html_codec_spec.rb +87 -0
- data/spec/codec/javascript_codec_spec.rb +45 -0
- data/spec/codec/mysql_codec_spec.rb +44 -0
- data/spec/codec/oracle_codec_spec.rb +23 -0
- data/spec/codec/os_codec_spec.rb +51 -0
- data/spec/codec/percent_codec_spec.rb +34 -0
- data/spec/codec/vbcript_codec_spec.rb +23 -0
- data/spec/codec/xml_codec_spec.rb +83 -0
- data/spec/owasp_esapi_encoder_spec.rb +226 -0
- data/spec/owasp_esapi_executor_spec.rb +9 -0
- data/spec/owasp_esapi_ruby_email_validator_spec.rb +39 -0
- data/spec/owasp_esapi_ruby_xss_sanitizer_spec.rb +66 -0
- data/spec/owasp_esapi_ruby_zipcode_validator_spec.rb +42 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/validator/base_rule_spec.rb +29 -0
- data/spec/validator/date_rule_spec.rb +40 -0
- data/spec/validator/float_rule_spec.rb +31 -0
- data/spec/validator/integer_rule_spec.rb +51 -0
- data/spec/validator/string_rule_spec.rb +103 -0
- data/spec/validator_skeleton.rb +150 -0
- metadata +235 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
# DateTime now has a to_time method injected in
|
4
|
+
class DateTime
|
5
|
+
def to_time
|
6
|
+
self.offset == 0 ? ::Time.utc(year, month, day, hour, min, sec) : self
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Owasp
|
11
|
+
module Esapi
|
12
|
+
module Validator
|
13
|
+
|
14
|
+
# A validator performs syntax and possibly semantic validation of a single
|
15
|
+
# piece of string data from an untrusted source. This class will return
|
16
|
+
# Time objects, as they are more flexible to reformat to for timezones
|
17
|
+
# and calendars
|
18
|
+
# Format variables, from rdoc
|
19
|
+
# %a - The abbreviated weekday name (``Sun'')
|
20
|
+
# %A - The full weekday name (``Sunday'')
|
21
|
+
# %b - The abbreviated month name (``Jan'')
|
22
|
+
# %B - The full month name (``January'')
|
23
|
+
# %c - The preferred local date and time representation
|
24
|
+
# %d - Day of the month (01..31)
|
25
|
+
# %H - Hour of the day, 24-hour clock (00..23)
|
26
|
+
# %I - Hour of the day, 12-hour clock (01..12)
|
27
|
+
# %j - Day of the year (001..366)
|
28
|
+
# %m - Month of the year (01..12)
|
29
|
+
# %M - Minute of the hour (00..59)**
|
30
|
+
# %p - Meridian indicator (``AM'' or ``PM'')
|
31
|
+
# %S - Second of the minute (00..60)
|
32
|
+
# %U - Week number of the current year,
|
33
|
+
# starting with the first Sunday as the first
|
34
|
+
# day of the first week (00..53)
|
35
|
+
# %W - Week number of the current year,
|
36
|
+
# starting with the first Monday as the first
|
37
|
+
# day of the first week (00..53)
|
38
|
+
# %w - Day of the week (Sunday is 0, 0..6)
|
39
|
+
# %x - Preferred representation for the date alone, no time
|
40
|
+
# %X - Preferred representation for the time alone, no date
|
41
|
+
# %y - Year without a century (00..99)
|
42
|
+
# %Y - Year with century
|
43
|
+
# %Z - Time zone name
|
44
|
+
# %% - Literal ``%'' character
|
45
|
+
class DateRule < BaseRule
|
46
|
+
attr :format
|
47
|
+
# Create a validator, if no format is specificed
|
48
|
+
# We assume %b $d, %Y i.e. September 11, 2001
|
49
|
+
def initialize(type, encoder = nil, dateformat = nil)
|
50
|
+
super(type,encoder)
|
51
|
+
@format = dateformat
|
52
|
+
@format = "%B %d, %Y" if dateformat.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
# Parse the input, raise exceptions if validation fails
|
56
|
+
# Returns a Time object
|
57
|
+
# see BaseRule
|
58
|
+
def valid(context,input)
|
59
|
+
# check for empty
|
60
|
+
if input.nil? or input.empty?
|
61
|
+
if @allow_nil
|
62
|
+
return nil
|
63
|
+
end
|
64
|
+
user = "#{context}: Input date required"
|
65
|
+
log = "Input date required: context=#{context}, input=#{input}"
|
66
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
67
|
+
end
|
68
|
+
# clean the input
|
69
|
+
clean = @encoder.canonicalize(input)
|
70
|
+
begin
|
71
|
+
return DateTime.strptime(clean,@format).to_time
|
72
|
+
rescue ArgumentError => failed
|
73
|
+
user="#{context}: Input date required"
|
74
|
+
log="Input date required: context=#{context}, input=#{input}"
|
75
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Calls valid, with any failures causing it to return a zero Time object
|
80
|
+
def sanitize(context,input)
|
81
|
+
d = Time.new(0)
|
82
|
+
begin
|
83
|
+
d = valid(context,input)
|
84
|
+
rescue ValidationException => e
|
85
|
+
end
|
86
|
+
return d
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'validator/generic_validator'
|
2
|
+
|
3
|
+
module Owasp
|
4
|
+
module Esapi
|
5
|
+
module Validator
|
6
|
+
class Email < GenericValidator
|
7
|
+
|
8
|
+
EMAIL_REGEX = "^(\\w)+[@](\\w)+[.]\\w{3}$"
|
9
|
+
# In order to make a strong validation for email addresses, it might be a good idea to
|
10
|
+
# make a check for the domain tld.
|
11
|
+
# This is a very optional and beta feature, so it is turned off by default.
|
12
|
+
attr_reader :validate_tld
|
13
|
+
|
14
|
+
def initialize(options=nil)
|
15
|
+
validate_tld = false
|
16
|
+
@matcher = EMAIL_REGEX
|
17
|
+
super(@matcher)
|
18
|
+
|
19
|
+
unless options.nil?
|
20
|
+
if options.has_key? "validate_tld"
|
21
|
+
validate_tld = options["validate_tld"]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Owasp
|
2
|
+
module Esapi
|
3
|
+
module Validator
|
4
|
+
class FloatRule < BaseRule
|
5
|
+
attr_accessor :min, :max
|
6
|
+
|
7
|
+
def initialize(type,encoder=nil,min=nil,max=nil)
|
8
|
+
super(type,encoder)
|
9
|
+
@min = min
|
10
|
+
@max = max
|
11
|
+
@min = Float::MIN if min.nil?
|
12
|
+
@max = Float::MAX if max.nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
# Validate the input context as an integer
|
16
|
+
def valid(context,input)
|
17
|
+
if input.nil?
|
18
|
+
if @allow_nil
|
19
|
+
return nil
|
20
|
+
end
|
21
|
+
puts "::#{input}::"
|
22
|
+
user = "#{context}: Input number required"
|
23
|
+
log = "Input number required: context=#{context}, input=#{input}"
|
24
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
25
|
+
end
|
26
|
+
clean = @encoder.canonicalize(input)
|
27
|
+
if @min > @max
|
28
|
+
user = "#{context}: Invalid number input: context"
|
29
|
+
log = "Validation parameter error for number: maxValue ( #{max}) must be greater than minValue ( #{min}) for #{context}"
|
30
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
31
|
+
end
|
32
|
+
begin
|
33
|
+
user = "Invalid number input must be between #{min} and #{max}: context=#{context}"
|
34
|
+
log = "Invalid number input must be between #{min} and #{max}: context=#{context}, input=#{input}"
|
35
|
+
i = Float(clean)
|
36
|
+
#check min
|
37
|
+
if i < @min
|
38
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
39
|
+
end
|
40
|
+
# check max
|
41
|
+
if i > @max
|
42
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
43
|
+
end
|
44
|
+
# check infinity
|
45
|
+
if i.infinite?
|
46
|
+
user = "#{context}: Invalid number input: context"
|
47
|
+
log = "Invalid double input is infinite context=#{context} input=#{input}"
|
48
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
49
|
+
end
|
50
|
+
# checknan
|
51
|
+
if i.nan?
|
52
|
+
user = "#{context}: Invalid number input: context"
|
53
|
+
log = "Invalid double input not a number context=#{context} input=#{input}"
|
54
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
55
|
+
end
|
56
|
+
return i
|
57
|
+
rescue Exception => e
|
58
|
+
user = "#{context}: Input number required"
|
59
|
+
log = "Input number required: context=#{context}, input=#{input}"
|
60
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# This will call valid and return a 0 if its invalid
|
65
|
+
def sanitize(context,input)
|
66
|
+
result = 0
|
67
|
+
begin
|
68
|
+
result= valid(context,input)
|
69
|
+
rescue ValidationException => e
|
70
|
+
end
|
71
|
+
result
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# This is the generic validator class that it will be the dad of all specific validation classes.
|
2
|
+
module Owasp
|
3
|
+
module Esapi
|
4
|
+
module Validator
|
5
|
+
class GenericValidator
|
6
|
+
|
7
|
+
attr_accessor :matcher
|
8
|
+
|
9
|
+
# Creates a new generic validator.
|
10
|
+
# @param [String] matcher the regular expression to be matched from this validator
|
11
|
+
def initialize(matcher)
|
12
|
+
@matcher = matcher
|
13
|
+
end
|
14
|
+
|
15
|
+
# Validate a string against the matcher
|
16
|
+
# @param [String] string the string that need to be validated
|
17
|
+
# @return [Boolean] true if the string matches the regular expression, false otherwise
|
18
|
+
def valid?(string)
|
19
|
+
r = Regexp.new(@matcher)
|
20
|
+
|
21
|
+
!(string =~ r).nil?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Owasp
|
2
|
+
module Esapi
|
3
|
+
module Validator
|
4
|
+
class IntegerRule < BaseRule
|
5
|
+
attr_accessor :min, :max
|
6
|
+
|
7
|
+
def initialize(type,encoder=nil,min=nil,max=nil)
|
8
|
+
super(type,encoder)
|
9
|
+
@min = min
|
10
|
+
@max = max
|
11
|
+
@min = Integer::MIN if min.nil?
|
12
|
+
@max = Integer::MAX if max.nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
# Validate the input context as an integer
|
16
|
+
def valid(context,input)
|
17
|
+
if input.nil?
|
18
|
+
if @allow_nil
|
19
|
+
return nil
|
20
|
+
end
|
21
|
+
user = "#{context}: Input number required"
|
22
|
+
log = "Input number required: context=#{context}, input=#{input}"
|
23
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
24
|
+
end
|
25
|
+
clean = @encoder.canonicalize(input)
|
26
|
+
if @min > @max
|
27
|
+
user = "#{context}: Invalid number input: context"
|
28
|
+
log = "Validation parameter error for number: maxValue ( #{max}) must be greater than minValue ( #{min}) for #{context}"
|
29
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
30
|
+
end
|
31
|
+
begin
|
32
|
+
user = "Invalid number input must be between #{min} and #{max}: context=#{context}"
|
33
|
+
log = "Invalid number input must be between #{min} and #{max}: context=#{context}, input=#{input}"
|
34
|
+
i = Integer(clean)
|
35
|
+
if i < @min
|
36
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
37
|
+
end
|
38
|
+
if i > @max
|
39
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
40
|
+
end
|
41
|
+
return i
|
42
|
+
rescue Exception => e
|
43
|
+
user = "#{context}: Input number required"
|
44
|
+
log = "Input number required: context=#{context}, input=#{input}"
|
45
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# SThis will call valid and return a 0 if its invalid
|
50
|
+
def sanitize(context,input)
|
51
|
+
result = 0
|
52
|
+
begin
|
53
|
+
result= valid(context,input)
|
54
|
+
rescue ValidationException => e
|
55
|
+
end
|
56
|
+
result
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module Owasp
|
4
|
+
module Esapi
|
5
|
+
module Validator
|
6
|
+
|
7
|
+
# A validator performs syntax and possibly semantic validation of a single
|
8
|
+
# piece of string data from an untrusted source.
|
9
|
+
class StringRule < BaseRule
|
10
|
+
attr_writer :min,:max,:canonicalize
|
11
|
+
|
12
|
+
# Create an instance of the String vlidator
|
13
|
+
# whitelist_pattern is an optionla white listing regex
|
14
|
+
def initialize(type,encoder = nil,whitelist_pattern = nil)
|
15
|
+
super(type,encoder)
|
16
|
+
@white_list = []
|
17
|
+
@black_list = []
|
18
|
+
@white_list << whitelist_pattern unless whitelist_pattern.nil?
|
19
|
+
@min = 0
|
20
|
+
@max = 0
|
21
|
+
@canonicalize = false
|
22
|
+
end
|
23
|
+
|
24
|
+
# Add a whitelist regex
|
25
|
+
def add_whitelist(p)
|
26
|
+
raise ArgumentError.new("Nil Pattern") if p.nil?
|
27
|
+
@white_list << create_regex(p)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Add a blacklist regex
|
31
|
+
def add_blacklist(p)
|
32
|
+
raise ArgumentError.new("Nil Pattern") if p.nil?
|
33
|
+
@black_list << create_regex(p)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Ensure we dont show the warnings to stderr, just fail the regexp
|
37
|
+
def create_regex(p) #:nodoc:
|
38
|
+
output = StringIO.open('','w')
|
39
|
+
$stderr = output
|
40
|
+
begin
|
41
|
+
r = /#{p}/
|
42
|
+
ensure
|
43
|
+
output.close
|
44
|
+
$stderr = STDERR
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Checks input against whitelists.
|
49
|
+
def check_white_list(context,input,original = nil)
|
50
|
+
original = input.dup if original.nil?
|
51
|
+
@white_list.each do |p|
|
52
|
+
match = p.match(input)
|
53
|
+
if match.nil? or not match[0].eql?(input)
|
54
|
+
# format user msg
|
55
|
+
user = "#{context}: Invalid input. Conform to #{p.to_s}"
|
56
|
+
user << " with a max length of #{@max}" unless @max == 0
|
57
|
+
# format log message
|
58
|
+
log = "Invalid input: context=#{context}, type=#{@name}, pattern=#{p.to_s}"
|
59
|
+
log << ", input=#{input}, original=#{original}"
|
60
|
+
# raise an error
|
61
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
input
|
65
|
+
end
|
66
|
+
|
67
|
+
# Checks input against blacklists.
|
68
|
+
def check_black_list(context,input,original = nil)
|
69
|
+
original = input.dup if original.nil?
|
70
|
+
@black_list.each do |p|
|
71
|
+
if p.match(input)
|
72
|
+
# format user msg
|
73
|
+
user = "#{context}: Invalid input. Dangerous input matching #{p.to_s}"
|
74
|
+
# format log message
|
75
|
+
log = "Dangerous input: context=#{context}, type=#{@name}, pattern=#{p.to_s}"
|
76
|
+
log << ", input=#{input}, original=#{original}"
|
77
|
+
# raise an error
|
78
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
input
|
82
|
+
end
|
83
|
+
|
84
|
+
# Checks input lengths
|
85
|
+
def check_length(context,input,original = nil)
|
86
|
+
original = input.dup if original.nil?
|
87
|
+
# check min value
|
88
|
+
if input.size < @min
|
89
|
+
user = "#{context}: Invalid input, The min length is #{@min} characters"
|
90
|
+
log = "Input didnt meet #{@min} chars by #{input.size}: context=#{context}, type=#{@name}, pattern=#{p.to_s}"
|
91
|
+
log << ", input=#{input}, original=#{original}"
|
92
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
93
|
+
end
|
94
|
+
# check max value
|
95
|
+
if input.size > @max and @max > 0
|
96
|
+
user = "#{context}: Invalid input, The max length is #{@max} characters"
|
97
|
+
log = "Input exceed #{@max} chars by #{input.size}: context=#{context}, type=#{@name}, pattern=#{p.to_s}"
|
98
|
+
log << ", input=#{input}, original=#{original}"
|
99
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
100
|
+
end
|
101
|
+
input
|
102
|
+
end
|
103
|
+
|
104
|
+
def check_empty(context,input,orig = nil)
|
105
|
+
return nil if @allow_nil and input.nil?
|
106
|
+
unless input.nil?
|
107
|
+
original = input.dup if original.nil?
|
108
|
+
return input unless input.empty?
|
109
|
+
end
|
110
|
+
user = "#{context}: Input required."
|
111
|
+
log = "Input required: context=#{context}, type=#{@name}, pattern=#{p.to_s}"
|
112
|
+
log << ", input=#{input}, original=#{original}"
|
113
|
+
raise Owasp::Esapi::ValidationException.new(user,log,context)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Remvoe any non alpha numerics form the string
|
117
|
+
def sanitize(context,input)
|
118
|
+
whitelist(input,Owasp::Esapi::Ecnoder::CHAR_ALPHANUMERIC)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Parse the input, raise exceptions if validation fails
|
122
|
+
# see BaseRule
|
123
|
+
def valid(context,input)
|
124
|
+
|
125
|
+
data = nil
|
126
|
+
return nil if check_empty(context,input).nil?
|
127
|
+
# check for pre-canonicalize if we are in sanitize mode
|
128
|
+
check_length(context,input) if @canonicalize
|
129
|
+
check_white_list(context,input) if @canonicalize
|
130
|
+
check_black_list(context,input) if @canonicalize
|
131
|
+
if @canonicalize
|
132
|
+
data = encoder.canonicalize(input)
|
133
|
+
else
|
134
|
+
data = input
|
135
|
+
end
|
136
|
+
# no check again after we figured otu canonicalization
|
137
|
+
return nil if check_empty(context,input).nil?
|
138
|
+
check_length(context,input)
|
139
|
+
check_white_list(context,input)
|
140
|
+
check_black_list(context,input)
|
141
|
+
data
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Owasp
|
2
|
+
module Esapi
|
3
|
+
module Validator
|
4
|
+
# List of Validation exceptions
|
5
|
+
# this list is indexed by the context
|
6
|
+
class ValidatorErrorList
|
7
|
+
|
8
|
+
# Create a new list
|
9
|
+
def initialize()
|
10
|
+
@errors = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
# Add an error to the list. We will raise ArgumentException if any of the following is true:
|
14
|
+
# 1. error is nil
|
15
|
+
# 2. context is nil
|
16
|
+
# 3. we already have an error for the given context
|
17
|
+
# 4. the error isnt a ValidationException
|
18
|
+
def <<(error)
|
19
|
+
raise ArgumentError.new("Invalid Error") if error.nil?
|
20
|
+
if error.instance_of?(ValidationException)
|
21
|
+
context = error.context
|
22
|
+
raise ArgumentError.new("Invalid context") if context.nil?
|
23
|
+
raise ArgumentError.new("Duplicate error") if @errors.has_key?(context)
|
24
|
+
@errors[context] = error
|
25
|
+
else
|
26
|
+
raise ArgumentError.new("Exception was not a ValdiaitonException")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return true if this list is empty
|
31
|
+
def empty?
|
32
|
+
@errors.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return the size of the list
|
36
|
+
def size
|
37
|
+
@errors.size
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return the array of errors in this list
|
41
|
+
def errors
|
42
|
+
@errors.values
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|