contrast-agent 6.4.0 → 6.5.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 +4 -4
- data/ext/cs__contrast_patch/cs__contrast_patch.c +14 -1
- data/lib/contrast/agent/assess/finalizers/hash.rb +1 -0
- data/lib/contrast/agent/assess/policy/propagation_method.rb +5 -1
- data/lib/contrast/agent/assess/policy/propagator/custom.rb +4 -0
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +5 -0
- data/lib/contrast/agent/assess/policy/propagator/split.rb +3 -0
- data/lib/contrast/agent/assess/policy/source_method.rb +5 -0
- data/lib/contrast/agent/assess/policy/trigger_method.rb +8 -2
- data/lib/contrast/agent/assess/tracker.rb +12 -0
- data/lib/contrast/agent/inventory/dependency_analysis.rb +2 -2
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +1 -1
- data/lib/contrast/agent/inventory/policy/datastores.rb +1 -1
- data/lib/contrast/agent/inventory/policy/policy.rb +1 -1
- data/lib/contrast/agent/patching/policy/method_policy.rb +3 -3
- data/lib/contrast/agent/reporting/reporter_heartbeat.rb +1 -3
- data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +17 -21
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +26 -3
- data/lib/contrast/agent/request_context.rb +8 -0
- data/lib/contrast/agent/service_heartbeat.rb +2 -3
- data/lib/contrast/agent/static_analysis.rb +1 -1
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent/worker_thread.rb +10 -0
- data/lib/contrast/components/agent.rb +51 -13
- data/lib/contrast/components/assess.rb +16 -0
- data/lib/contrast/components/contrast_service.rb +1 -1
- data/lib/contrast/components/heap_dump.rb +51 -1
- data/lib/contrast/components/inventory.rb +19 -13
- data/lib/contrast/components/logger.rb +18 -0
- data/lib/contrast/config/assess_configuration.rb +28 -0
- data/lib/contrast/config/base_configuration.rb +8 -2
- data/lib/contrast/config/root_configuration.rb +11 -8
- data/lib/contrast/config/service_configuration.rb +4 -4
- data/lib/contrast/config.rb +0 -6
- data/lib/contrast/extension/object.rb +19 -0
- data/lib/contrast/framework/rails/support.rb +4 -1
- data/lib/contrast/logger/log.rb +2 -1
- data/lib/contrast/utils/assess/event_limit_utils.rb +96 -0
- data/lib/contrast/utils/assess/propagation_method_utils.rb +27 -7
- data/lib/contrast/utils/log_utils.rb +2 -2
- data/lib/contrast/utils/patching/policy/patch_utils.rb +1 -1
- data/lib/contrast.rb +4 -19
- data/resources/assess/policy.json +4 -12
- data/ruby-agent.gemspec +2 -0
- metadata +43 -17
- data/lib/contrast/config/agent_configuration.rb +0 -63
- data/lib/contrast/config/heap_dump_configuration.rb +0 -59
- data/lib/contrast/config/inventory_configuration.rb +0 -33
- data/lib/contrast/config/logger_configuration.rb +0 -26
@@ -19,7 +19,7 @@ module Contrast
|
|
19
19
|
|
20
20
|
private
|
21
21
|
|
22
|
-
def build path: STDOUT_STR, level_const: DEFAULT_LEVEL
|
22
|
+
def build path: STDOUT_STR, level_const: DEFAULT_LEVEL, progname: PROGNAME
|
23
23
|
logger = case path
|
24
24
|
when STDOUT_STR, STDERR_STR
|
25
25
|
::Ougai::Logger.new(Object.cs__const_get(path))
|
@@ -27,7 +27,7 @@ module Contrast
|
|
27
27
|
::Ougai::Logger.new(path)
|
28
28
|
end
|
29
29
|
add_contrast_loggers(logger)
|
30
|
-
logger.progname =
|
30
|
+
logger.progname = progname
|
31
31
|
logger.level = level_const
|
32
32
|
logger.formatter = Contrast::Logger::Format.new
|
33
33
|
logger.formatter.datetime_format = DATE_TIME_FORMAT
|
@@ -95,7 +95,7 @@ module Contrast
|
|
95
95
|
# @param object [Object] The object on which the method is invoked, typically what would be returned by self.
|
96
96
|
# @param args [Array<Object>] The arguments passed to the method being invoked.
|
97
97
|
def apply_inventory method_policy, method, exception, object, args
|
98
|
-
return unless ::Contrast::INVENTORY.
|
98
|
+
return unless ::Contrast::INVENTORY.enable
|
99
99
|
|
100
100
|
apply_trigger_only(method_policy&.inventory_node, method, exception, object, args)
|
101
101
|
end
|
data/lib/contrast.rb
CHANGED
@@ -4,22 +4,7 @@
|
|
4
4
|
# Used to prevent deprecation warnings from flooding stdout
|
5
5
|
ENV['PB_IGNORE_DEPRECATIONS'] = 'true'
|
6
6
|
|
7
|
-
|
8
|
-
# changing expected method parity/behavior which in turn prevents us from being
|
9
|
-
# able to reliably use affected methods.
|
10
|
-
# We alias these method so that we always have access to them.
|
11
|
-
#
|
12
|
-
# Because we use these methods in constructing classes (e.g., calling #freeze
|
13
|
-
# on constants within class definitions) we do this aliasing ASAP.
|
14
|
-
class Object
|
15
|
-
alias_method :cs__class, :class
|
16
|
-
alias_method :cs__freeze, :freeze
|
17
|
-
alias_method :cs__frozen?, :frozen?
|
18
|
-
alias_method :cs__is_a?, :is_a?
|
19
|
-
alias_method :cs__method, :method
|
20
|
-
alias_method :cs__respond_to?, :respond_to?
|
21
|
-
alias_method :cs__singleton_class, :singleton_class
|
22
|
-
end
|
7
|
+
require 'contrast/extension/object'
|
23
8
|
|
24
9
|
# ActiveRecord gives access to the `String#blank?` method, which we've started using. We need to make sure that method
|
25
10
|
# actually exists.
|
@@ -92,9 +77,9 @@ module Contrast
|
|
92
77
|
SETTINGS = Contrast::Components::Settings::Interface.new
|
93
78
|
ASSESS = Contrast::Components::Assess::Interface.new
|
94
79
|
PROTECT = Contrast::Components::Protect::Interface.new
|
95
|
-
INVENTORY =
|
96
|
-
|
97
|
-
|
80
|
+
INVENTORY = CONFIG.root.inventory
|
81
|
+
AGENT = CONFIG.root.agent
|
82
|
+
LOGGER = AGENT.logger
|
98
83
|
CONTRAST_SERVICE = Contrast::Components::ContrastService::Interface.new
|
99
84
|
APP_CONTEXT = Contrast::Components::AppContext::Interface.new
|
100
85
|
end
|
@@ -692,15 +692,7 @@
|
|
692
692
|
"action":"CUSTOM",
|
693
693
|
"patch_class": "Contrast::Agent::Assess::Policy::Propagator::MatchData",
|
694
694
|
"patch_method": "values_at_tagger"
|
695
|
-
},
|
696
|
-
"class_name":"String",
|
697
|
-
"instance_method": true,
|
698
|
-
"method_visibility": "public",
|
699
|
-
"method_name":"to_sym",
|
700
|
-
"source":"O",
|
701
|
-
"target":"R",
|
702
|
-
"action":"KEEP"
|
703
|
-
}, {
|
695
|
+
},{
|
704
696
|
"class_name": "String",
|
705
697
|
"instance_method": true,
|
706
698
|
"method_visibility": "public",
|
@@ -1860,9 +1852,9 @@
|
|
1860
1852
|
"source": "P0"
|
1861
1853
|
},{
|
1862
1854
|
"class_name": "Excon",
|
1863
|
-
"instance_method":
|
1864
|
-
"method_visibility": "
|
1865
|
-
"method_name": "
|
1855
|
+
"instance_method": false,
|
1856
|
+
"method_visibility": "public",
|
1857
|
+
"method_name": "new",
|
1866
1858
|
"source": "P0"
|
1867
1859
|
},
|
1868
1860
|
{
|
data/ruby-agent.gemspec
CHANGED
@@ -82,6 +82,8 @@ def self.add_specs spec
|
|
82
82
|
spec.add_development_dependency 'rspec-rails', '5.0'
|
83
83
|
spec.add_development_dependency 'tzinfo-data' # Alpine rspec-rails requirement.
|
84
84
|
spec.add_development_dependency 'warning'
|
85
|
+
spec.add_development_dependency 'typhoeus', '~> 1.4'
|
86
|
+
spec.add_development_dependency 'excon', '~> 0.92.3'
|
85
87
|
end
|
86
88
|
|
87
89
|
def self.add_coverage spec
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: contrast-agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.
|
4
|
+
version: 6.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- galen.palmer@contrastsecurity.com
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: exe
|
15
15
|
cert_chain: []
|
16
|
-
date: 2022-06-
|
16
|
+
date: 2022-06-29 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: bundler
|
@@ -567,6 +567,34 @@ dependencies:
|
|
567
567
|
- - ">="
|
568
568
|
- !ruby/object:Gem::Version
|
569
569
|
version: '0'
|
570
|
+
- !ruby/object:Gem::Dependency
|
571
|
+
name: typhoeus
|
572
|
+
requirement: !ruby/object:Gem::Requirement
|
573
|
+
requirements:
|
574
|
+
- - "~>"
|
575
|
+
- !ruby/object:Gem::Version
|
576
|
+
version: '1.4'
|
577
|
+
type: :development
|
578
|
+
prerelease: false
|
579
|
+
version_requirements: !ruby/object:Gem::Requirement
|
580
|
+
requirements:
|
581
|
+
- - "~>"
|
582
|
+
- !ruby/object:Gem::Version
|
583
|
+
version: '1.4'
|
584
|
+
- !ruby/object:Gem::Dependency
|
585
|
+
name: excon
|
586
|
+
requirement: !ruby/object:Gem::Requirement
|
587
|
+
requirements:
|
588
|
+
- - "~>"
|
589
|
+
- !ruby/object:Gem::Version
|
590
|
+
version: 0.92.3
|
591
|
+
type: :development
|
592
|
+
prerelease: false
|
593
|
+
version_requirements: !ruby/object:Gem::Requirement
|
594
|
+
requirements:
|
595
|
+
- - "~>"
|
596
|
+
- !ruby/object:Gem::Version
|
597
|
+
version: 0.92.3
|
570
598
|
- !ruby/object:Gem::Dependency
|
571
599
|
name: zlib
|
572
600
|
requirement: !ruby/object:Gem::Requirement
|
@@ -637,22 +665,22 @@ executables:
|
|
637
665
|
- contrast_service
|
638
666
|
extensions:
|
639
667
|
- ext/cs__common/extconf.rb
|
640
|
-
- ext/
|
641
|
-
- ext/cs__assess_basic_object/extconf.rb
|
642
|
-
- ext/cs__assess_hash/extconf.rb
|
643
|
-
- ext/cs__assess_fiber_track/extconf.rb
|
644
|
-
- ext/cs__assess_string_interpolation/extconf.rb
|
645
|
-
- ext/cs__assess_kernel/extconf.rb
|
646
|
-
- ext/cs__assess_marshal_module/extconf.rb
|
647
|
-
- ext/cs__contrast_patch/extconf.rb
|
668
|
+
- ext/cs__assess_yield_track/extconf.rb
|
648
669
|
- ext/cs__os_information/extconf.rb
|
670
|
+
- ext/cs__contrast_patch/extconf.rb
|
649
671
|
- ext/cs__assess_array/extconf.rb
|
672
|
+
- ext/cs__assess_test/extconf.rb
|
673
|
+
- ext/cs__assess_string_interpolation/extconf.rb
|
674
|
+
- ext/cs__assess_fiber_track/extconf.rb
|
675
|
+
- ext/cs__assess_marshal_module/extconf.rb
|
676
|
+
- ext/cs__assess_basic_object/extconf.rb
|
677
|
+
- ext/cs__assess_regexp/extconf.rb
|
678
|
+
- ext/cs__assess_string/extconf.rb
|
650
679
|
- ext/cs__tests/extconf.rb
|
651
680
|
- ext/cs__assess_module/extconf.rb
|
652
|
-
- ext/
|
653
|
-
- ext/
|
681
|
+
- ext/cs__assess_hash/extconf.rb
|
682
|
+
- ext/cs__assess_kernel/extconf.rb
|
654
683
|
- ext/cs__scope/extconf.rb
|
655
|
-
- ext/cs__assess_test/extconf.rb
|
656
684
|
extra_rdoc_files: []
|
657
685
|
files:
|
658
686
|
- ".clang-format"
|
@@ -1164,7 +1192,6 @@ files:
|
|
1164
1192
|
- lib/contrast/components/scope.rb
|
1165
1193
|
- lib/contrast/components/settings.rb
|
1166
1194
|
- lib/contrast/config.rb
|
1167
|
-
- lib/contrast/config/agent_configuration.rb
|
1168
1195
|
- lib/contrast/config/api_configuration.rb
|
1169
1196
|
- lib/contrast/config/api_proxy_configuration.rb
|
1170
1197
|
- lib/contrast/config/application_configuration.rb
|
@@ -1174,9 +1201,6 @@ files:
|
|
1174
1201
|
- lib/contrast/config/certification_configuration.rb
|
1175
1202
|
- lib/contrast/config/env_variables.rb
|
1176
1203
|
- lib/contrast/config/exception_configuration.rb
|
1177
|
-
- lib/contrast/config/heap_dump_configuration.rb
|
1178
|
-
- lib/contrast/config/inventory_configuration.rb
|
1179
|
-
- lib/contrast/config/logger_configuration.rb
|
1180
1204
|
- lib/contrast/config/protect_configuration.rb
|
1181
1205
|
- lib/contrast/config/protect_rule_configuration.rb
|
1182
1206
|
- lib/contrast/config/protect_rules_configuration.rb
|
@@ -1202,6 +1226,7 @@ files:
|
|
1202
1226
|
- lib/contrast/extension/extension.rb
|
1203
1227
|
- lib/contrast/extension/inventory.rb
|
1204
1228
|
- lib/contrast/extension/module.rb
|
1229
|
+
- lib/contrast/extension/object.rb
|
1205
1230
|
- lib/contrast/extension/protect.rb
|
1206
1231
|
- lib/contrast/extension/protect/psych.rb
|
1207
1232
|
- lib/contrast/extension/thread.rb
|
@@ -1230,6 +1255,7 @@ files:
|
|
1230
1255
|
- lib/contrast/security_exception.rb
|
1231
1256
|
- lib/contrast/tasks/config.rb
|
1232
1257
|
- lib/contrast/tasks/service.rb
|
1258
|
+
- lib/contrast/utils/assess/event_limit_utils.rb
|
1233
1259
|
- lib/contrast/utils/assess/object_store.rb
|
1234
1260
|
- lib/contrast/utils/assess/propagation_method_utils.rb
|
1235
1261
|
- lib/contrast/utils/assess/property/tagged_utils.rb
|
@@ -1,63 +0,0 @@
|
|
1
|
-
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'contrast/config/service_configuration'
|
5
|
-
require 'contrast/config/logger_configuration'
|
6
|
-
require 'contrast/config/ruby_configuration'
|
7
|
-
require 'contrast/config/heap_dump_configuration'
|
8
|
-
require 'contrast/config/api_configuration'
|
9
|
-
|
10
|
-
module Contrast
|
11
|
-
module Config
|
12
|
-
# Common Configuration settings. Those in this section pertain to the core functionality of the Agent.
|
13
|
-
class AgentConfiguration
|
14
|
-
include Contrast::Config::BaseConfiguration
|
15
|
-
|
16
|
-
# @return [Boolean, nil]
|
17
|
-
attr_accessor :enable
|
18
|
-
# @return [Boolean, nil]
|
19
|
-
attr_accessor :omit_body
|
20
|
-
# @return [Contrast::Config::RubyConfiguration]
|
21
|
-
attr_writer :ruby
|
22
|
-
# @return [Contrast::Config::ServiceConfiguration]
|
23
|
-
attr_writer :service
|
24
|
-
# @return [ Contrast::Config::LoggerConfiguration]
|
25
|
-
attr_writer :logger
|
26
|
-
# @return [Contrast::Config::HeapDumpConfiguration]
|
27
|
-
attr_writer :heap_dump
|
28
|
-
|
29
|
-
def initialize hsh = {}
|
30
|
-
return unless hsh
|
31
|
-
|
32
|
-
@enable = hsh[:enable]
|
33
|
-
@start_bundled_service = hsh[:start_bundled_service]
|
34
|
-
@omit_body = hsh[:omit_body]
|
35
|
-
@service = Contrast::Config::ServiceConfiguration.new(hsh[:service])
|
36
|
-
@logger = Contrast::Config::LoggerConfiguration.new(hsh[:logger])
|
37
|
-
@ruby = Contrast::Config::RubyConfiguration.new(hsh[:ruby])
|
38
|
-
@heap_dump = Contrast::Config::HeapDumpConfiguration.new(hsh[:heap_dump])
|
39
|
-
end
|
40
|
-
|
41
|
-
# @return [Boolean, true]
|
42
|
-
def start_bundled_service
|
43
|
-
@start_bundled_service.nil? ? true : @start_bundled_service
|
44
|
-
end
|
45
|
-
|
46
|
-
def service
|
47
|
-
@service ||= Contrast::Config::ServiceConfiguration.new
|
48
|
-
end
|
49
|
-
|
50
|
-
def logger
|
51
|
-
@logger ||= Contrast::Config::LoggerConfiguration.new
|
52
|
-
end
|
53
|
-
|
54
|
-
def ruby
|
55
|
-
@ruby ||= Contrast::Config::RubyConfiguration.new
|
56
|
-
end
|
57
|
-
|
58
|
-
def heap_dump
|
59
|
-
@heap_dump ||= Contrast::Config::HeapDumpConfiguration.new
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
@@ -1,59 +0,0 @@
|
|
1
|
-
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
module Contrast
|
5
|
-
module Config
|
6
|
-
# Common Configuration settings. Those in this section pertain to the Heap Dump collection functionality of the
|
7
|
-
# Agent.
|
8
|
-
class HeapDumpConfiguration
|
9
|
-
include Contrast::Config::BaseConfiguration
|
10
|
-
|
11
|
-
DEFAULT_PATH = 'contrast_heap_dumps' # saved
|
12
|
-
DEFAULT_MS = 10_000
|
13
|
-
DEFAULT_COUNT = 5
|
14
|
-
|
15
|
-
attr_writer :enable, :path, :delay_ms, :window_ms, :count, :clean
|
16
|
-
|
17
|
-
def initialize hsh = {}
|
18
|
-
return unless hsh
|
19
|
-
|
20
|
-
@enable = hsh[:enable]
|
21
|
-
@path = hsh[:path]
|
22
|
-
@delay_ms = hsh[:delay_ms]
|
23
|
-
@window_ms = hsh[:window_ms]
|
24
|
-
@count = hsh[:count]
|
25
|
-
@clean = hsh[:clean]
|
26
|
-
end
|
27
|
-
|
28
|
-
# @return [Boolean, Contrast::Utils::ObjectShare::FALSE] should dumps be taken
|
29
|
-
def enable
|
30
|
-
@enable.nil? ? Contrast::Utils::ObjectShare::FALSE : @enable
|
31
|
-
end
|
32
|
-
|
33
|
-
# @return [String, DEFAULT_PATH] dir to which dumps should be
|
34
|
-
def path
|
35
|
-
@path ||= DEFAULT_PATH
|
36
|
-
end
|
37
|
-
|
38
|
-
# @return [Integer, DEFAULT_MS] time, in ms, after initialization
|
39
|
-
def delay_ms
|
40
|
-
@delay_ms ||= DEFAULT_MS
|
41
|
-
end
|
42
|
-
|
43
|
-
# @return [Integer, DEFAULT_MS] ms between each dump
|
44
|
-
def window_ms
|
45
|
-
@window_ms ||= DEFAULT_MS
|
46
|
-
end
|
47
|
-
|
48
|
-
# @return [Integer, DEFAULT_MS] number of dumps to take
|
49
|
-
def count
|
50
|
-
@count ||= DEFAULT_COUNT
|
51
|
-
end
|
52
|
-
|
53
|
-
# @return [Boolean, Contrast::Utils::ObjectShare::FALSE] remove temporary objects or not
|
54
|
-
def clean
|
55
|
-
@clean.nil? ? Contrast::Utils::ObjectShare::FALSE : @clean
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
module Contrast
|
5
|
-
module Config
|
6
|
-
# Common Configuration settings. Those in this section pertain to the inventory functionality of the Agent.
|
7
|
-
class InventoryConfiguration
|
8
|
-
include Contrast::Config::BaseConfiguration
|
9
|
-
|
10
|
-
# @return [Array, nil] tags
|
11
|
-
attr_accessor :tags
|
12
|
-
attr_writer :enable, :analyze_libraries
|
13
|
-
|
14
|
-
def initialize hsh = {}
|
15
|
-
return unless hsh
|
16
|
-
|
17
|
-
@enable = hsh[:enable]
|
18
|
-
@analyze_libraries = hsh[:analyze_libraries]
|
19
|
-
@tags = hsh[:tags]
|
20
|
-
end
|
21
|
-
|
22
|
-
# @return [Boolean, true]
|
23
|
-
def enable
|
24
|
-
@enable.nil? ? true : @enable
|
25
|
-
end
|
26
|
-
|
27
|
-
# @return [Boolean, true]
|
28
|
-
def analyze_libraries
|
29
|
-
@analyze_libraries.nil? ? true : @analyze_libraries
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
module Contrast
|
5
|
-
module Config
|
6
|
-
# Common Configuration settings. Those in this section pertain to the logging in the Agent.
|
7
|
-
class LoggerConfiguration
|
8
|
-
include Contrast::Config::BaseConfiguration
|
9
|
-
|
10
|
-
# @return [String, nil]
|
11
|
-
attr_accessor :path
|
12
|
-
# @return [String, nil]
|
13
|
-
attr_accessor :level
|
14
|
-
# @return [String, nil]
|
15
|
-
attr_accessor :progname
|
16
|
-
|
17
|
-
def initialize hsh = {}
|
18
|
-
return unless hsh
|
19
|
-
|
20
|
-
@path = hsh[:path]
|
21
|
-
@level = hsh[:level]
|
22
|
-
@progname = hsh[:progname]
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|