owasp-esapi-ruby 0.30.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.
- 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
|