contrast-agent 3.15.0 → 3.16.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/lib/contrast/agent.rb +2 -9
- data/lib/contrast/agent/assess/contrast_event.rb +142 -70
- data/lib/contrast/agent/assess/events/source_event.rb +1 -1
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +10 -3
- data/lib/contrast/agent/assess/policy/policy_node.rb +15 -10
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +7 -1
- data/lib/contrast/agent/assess/policy/propagator/insert.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +0 -3
- data/lib/contrast/agent/assess/policy/propagator/select.rb +1 -3
- data/lib/contrast/agent/assess/policy/propagator/splat.rb +0 -5
- data/lib/contrast/agent/assess/policy/propagator/split.rb +12 -13
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +21 -14
- data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +4 -5
- data/lib/contrast/agent/assess/policy/trigger_method.rb +39 -14
- data/lib/contrast/agent/assess/policy/trigger_node.rb +31 -37
- data/lib/contrast/agent/assess/property/evented.rb +5 -18
- data/lib/contrast/agent/assess/property/tagged.rb +9 -3
- data/lib/contrast/agent/assess/property/updated.rb +0 -5
- data/lib/contrast/agent/assess/rule/provider/hardcoded_key.rb +58 -5
- data/lib/contrast/agent/assess/rule/provider/hardcoded_password.rb +23 -8
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +82 -14
- data/lib/contrast/agent/assess/tag.rb +1 -1
- data/lib/contrast/agent/at_exit_hook.rb +5 -5
- data/lib/contrast/agent/patching/policy/after_load_patch.rb +5 -5
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +20 -20
- data/lib/contrast/agent/patching/policy/module_policy.rb +10 -10
- data/lib/contrast/agent/patching/policy/policy.rb +16 -2
- data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +3 -5
- data/lib/contrast/agent/protect/policy/applies_xxe_rule.rb +1 -1
- data/lib/contrast/agent/protect/rule/no_sqli/mongo_no_sql_scanner.rb +1 -0
- data/lib/contrast/agent/request.rb +34 -34
- data/lib/contrast/agent/static_analysis.rb +6 -6
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/communication/socket_client.rb +36 -1
- data/lib/contrast/api/decorators/address.rb +13 -13
- data/lib/contrast/api/decorators/message.rb +1 -0
- data/lib/contrast/api/decorators/trace_event.rb +20 -18
- data/lib/contrast/components/app_context.rb +39 -30
- data/lib/contrast/components/contrast_service.rb +9 -9
- data/lib/contrast/components/settings.rb +20 -23
- data/lib/contrast/config/service_configuration.rb +4 -2
- data/lib/contrast/configuration.rb +1 -1
- data/lib/contrast/extension/assess/array.rb +7 -3
- data/lib/contrast/extension/assess/erb.rb +5 -0
- data/lib/contrast/extension/assess/eval_trigger.rb +6 -6
- data/lib/contrast/extension/assess/exec_trigger.rb +1 -1
- data/lib/contrast/extension/assess/fiber.rb +3 -3
- data/lib/contrast/extension/assess/hash.rb +3 -3
- data/lib/contrast/extension/assess/kernel.rb +18 -20
- data/lib/contrast/extension/assess/marshal.rb +8 -4
- data/lib/contrast/extension/assess/regexp.rb +3 -3
- data/lib/contrast/extension/assess/string.rb +13 -11
- data/lib/contrast/extension/protect/kernel.rb +3 -3
- data/lib/contrast/framework/base_support.rb +1 -1
- data/lib/contrast/framework/manager.rb +3 -3
- data/lib/contrast/framework/rack/patch/session_cookie.rb +9 -9
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +13 -13
- data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +10 -10
- data/lib/contrast/framework/rails/patch/support.rb +1 -1
- data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +11 -11
- data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +12 -12
- data/lib/contrast/framework/rails/rewrite/active_record_named.rb +3 -3
- data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +12 -12
- data/lib/contrast/framework/sinatra/patch/base.rb +11 -11
- data/lib/contrast/framework/sinatra/support.rb +4 -4
- data/lib/contrast/logger/log.rb +7 -2
- data/lib/contrast/utils/invalid_configuration_util.rb +2 -5
- data/resources/assess/policy.json +31 -12
- data/ruby-agent.gemspec +4 -3
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +31 -17
@@ -38,15 +38,18 @@ module Contrast
|
|
38
38
|
NON_PASSWORD_PARTIAL_NAMES.none? { |name| constant_string.index(name) }
|
39
39
|
end
|
40
40
|
|
41
|
-
#
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
# Determine if the given value node violates the hardcode key rule
|
42
|
+
# @param value_node [RubyVM::AbstractSyntaxTree::Node] the node to
|
43
|
+
# evaluate
|
44
|
+
# @return [Boolean]
|
45
|
+
def value_node_passes? value_node
|
46
|
+
# If it's a freeze call, then evaluate the entity being frozen
|
47
|
+
value_node = value_node.children[0] if freeze_call?(value_node)
|
48
|
+
return false unless value_node.type == :STR
|
45
49
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
!probably_property_name?(value)
|
50
|
+
# https://www.rubydoc.info/gems/ruby-internal/Node/STR
|
51
|
+
string = value_node.children[0]
|
52
|
+
!probably_property_name?(string)
|
50
53
|
end
|
51
54
|
|
52
55
|
# If a field name matches an expected password field, we'll check it's
|
@@ -65,6 +68,18 @@ module Contrast
|
|
65
68
|
def redacted_marker
|
66
69
|
REDACTED_MARKER
|
67
70
|
end
|
71
|
+
|
72
|
+
# TODO: RUBY-1014 remove `#value_type_passes?` and `#value_passes?`
|
73
|
+
# If the value is a string, it passes for this rule
|
74
|
+
def value_type_passes? value
|
75
|
+
value.is_a?(String)
|
76
|
+
end
|
77
|
+
|
78
|
+
# If the value probably isn't a property name, it passes for this
|
79
|
+
# rule
|
80
|
+
def value_passes? value
|
81
|
+
!probably_property_name?(value)
|
82
|
+
end
|
68
83
|
end
|
69
84
|
end
|
70
85
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require 'contrast/agent/assess/policy/trigger_method'
|
4
5
|
require 'contrast/components/interface'
|
5
6
|
require 'contrast/extension/module'
|
6
7
|
|
@@ -9,14 +10,13 @@ module Contrast
|
|
9
10
|
module Assess
|
10
11
|
module Rule
|
11
12
|
module Provider
|
12
|
-
# Hardcoded rules detect if any secret value has been written
|
13
|
-
# sourcecode of the application. To use this base
|
14
|
-
# implement
|
13
|
+
# Hardcoded rules detect if any secret value has been written
|
14
|
+
# directly into the sourcecode of the application. To use this base
|
15
|
+
# class, a provider must implement three methods:
|
15
16
|
# 1) name_passes? : does the constant name match a given value set
|
16
|
-
# 2)
|
17
|
-
#
|
18
|
-
# 3)
|
19
|
-
# 4) redacted_marker : the value to plug in for the obfuscated value
|
17
|
+
# 2) value_node_passes? : does the value of the constant match a
|
18
|
+
# given value set
|
19
|
+
# 3) redacted_marker : the value to plug in for the obfuscated value
|
20
20
|
module HardcodedValueRule
|
21
21
|
include Contrast::Components::Interface
|
22
22
|
access_component :analysis, :app_context, :logging
|
@@ -25,6 +25,7 @@ module Contrast
|
|
25
25
|
!ASSESS.enabled? || ASSESS.rule_disabled?(rule_id)
|
26
26
|
end
|
27
27
|
|
28
|
+
# TODO: RUBY-1014 - remove `#analyze`
|
28
29
|
COMMON_CONSTANTS = %i[
|
29
30
|
CONTRAST_ASSESS_POLICY_STATUS
|
30
31
|
VERSION
|
@@ -69,10 +70,30 @@ module Contrast
|
|
69
70
|
# if it looks like a placeholder / pointer to a config, skip it
|
70
71
|
next unless value_passes?(value)
|
71
72
|
|
72
|
-
|
73
|
+
build_finding(clazz, constant_string)
|
73
74
|
end
|
74
75
|
end
|
75
76
|
|
77
|
+
# Parse the file pertaining to the given TracePoint to walk its AST
|
78
|
+
# to determine if a Constant is hardcoded. For our purposes, this
|
79
|
+
# hard coding means directly set rather than as an interpolated
|
80
|
+
# String or through a method call.
|
81
|
+
#
|
82
|
+
# Note: This is a top layer check, we make no assertions about what
|
83
|
+
# the methods or interpolations do. Their presence, even if only
|
84
|
+
# calling a hardcoded thing, causes this check to not report.
|
85
|
+
#
|
86
|
+
# @param trace_point [TracePoint] the TracePoint event created on
|
87
|
+
# the :end of a Module being loaded
|
88
|
+
def parse trace_point
|
89
|
+
return if disabled?
|
90
|
+
|
91
|
+
ast = RubyVM::AbstractSyntaxTree.parse_file(trace_point.path)
|
92
|
+
parse_ast(trace_point.self, ast)
|
93
|
+
rescue StandardError => e
|
94
|
+
logger.error('Unable to parse AST for hardcoded keys', e, module: trace_point.self)
|
95
|
+
end
|
96
|
+
|
76
97
|
# Constants can be variable or classes defined in the given
|
77
98
|
# class. We ONLY want the variables, which should be defined in
|
78
99
|
# the MACRO_CASE (upper case & underscore format)
|
@@ -90,7 +111,57 @@ module Contrast
|
|
90
111
|
|
91
112
|
private
|
92
113
|
|
93
|
-
|
114
|
+
# @param mod [Module] the module to which this AST pertains
|
115
|
+
# @param ast [RubyVM::AbstractSyntaxTree::Node, Object] a node
|
116
|
+
# within the AST, which may be a leaf, so any Object
|
117
|
+
def parse_ast mod, ast
|
118
|
+
return unless ast.cs__is_a?(RubyVM::AbstractSyntaxTree::Node)
|
119
|
+
return unless ast.cs__respond_to?(:children)
|
120
|
+
|
121
|
+
children = ast.children
|
122
|
+
return unless children.any?
|
123
|
+
|
124
|
+
ast.children.each do |child|
|
125
|
+
parse_ast(mod, child)
|
126
|
+
end
|
127
|
+
|
128
|
+
# https://www.rubydoc.info/gems/ruby-internal/Node/CDECL
|
129
|
+
return unless ast.type == :CDECL
|
130
|
+
|
131
|
+
# The CDECL Node has two children, the first being the Constant
|
132
|
+
# name as a symbol, the second as the value to assign to that
|
133
|
+
# constant
|
134
|
+
children = ast.children
|
135
|
+
name = children[0].to_s
|
136
|
+
# If that constant name doesn't pass our checks, move on.
|
137
|
+
return unless name_passes?(name)
|
138
|
+
|
139
|
+
value = children[1]
|
140
|
+
# The assignment node could be a direct value or a call of some
|
141
|
+
# sort. We leave it to each rule to properly handle these nodes.
|
142
|
+
return unless value_node_passes?(value)
|
143
|
+
|
144
|
+
build_finding(mod, name)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Constants can be set as frozen directly. We need to account for
|
148
|
+
# this change as it means the Node given to the :CDECL call will be
|
149
|
+
# a :CALL, not a constant.
|
150
|
+
#
|
151
|
+
# @param value_node [RubyVM::AbstractSyntaxTree::Node] the node to
|
152
|
+
# evaluate
|
153
|
+
# @return [Boolean] is this a freeze call or not
|
154
|
+
def freeze_call? value_node
|
155
|
+
return false unless value_node.type == :CALL
|
156
|
+
|
157
|
+
children = value_node.children
|
158
|
+
return false unless children
|
159
|
+
return false unless children.length >= 2
|
160
|
+
|
161
|
+
children[1] == :freeze
|
162
|
+
end
|
163
|
+
|
164
|
+
def build_finding clazz, constant_string
|
94
165
|
class_name = clazz.cs__name
|
95
166
|
|
96
167
|
finding = Contrast::Api::Dtm::Finding.new
|
@@ -104,13 +175,10 @@ module Contrast
|
|
104
175
|
hash = Contrast::Utils::HashDigest.generate_class_scanning_hash(finding)
|
105
176
|
finding.hash_code = Contrast::Utils::StringUtils.protobuf_safe_string(hash)
|
106
177
|
finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
|
107
|
-
|
108
|
-
activity = Contrast::Api::Dtm::Activity.new
|
109
|
-
activity.findings << finding
|
110
|
-
|
111
|
-
Contrast::Agent.messaging_queue.send_event_eventually(activity)
|
178
|
+
Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding)
|
112
179
|
rescue StandardError => e
|
113
180
|
logger.error('Unable to build a finding for Hardcoded Rule', e)
|
181
|
+
nil
|
114
182
|
end
|
115
183
|
end
|
116
184
|
end
|
@@ -14,7 +14,7 @@ module Contrast
|
|
14
14
|
|
15
15
|
# Initialize a new tag
|
16
16
|
#
|
17
|
-
# @param label [String] the
|
17
|
+
# @param label [String] the label of the tag
|
18
18
|
# @param length [Integer] the length of the string described with this
|
19
19
|
# tag
|
20
20
|
# @param start_idx [Integer] (0) the starting position in the string for
|
@@ -11,11 +11,11 @@ module Contrast
|
|
11
11
|
access_component :logging
|
12
12
|
def self.exit_hook
|
13
13
|
@_exit_hook ||= begin
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
at_exit do
|
15
|
+
on_exit
|
16
|
+
end
|
17
|
+
true
|
18
|
+
end
|
19
19
|
end
|
20
20
|
|
21
21
|
# Actions to take when a process exits. Typically called from our
|
@@ -15,7 +15,7 @@ module Contrast
|
|
15
15
|
access_component :scope
|
16
16
|
attr_reader :applied, :module_name, :instrumentation_file_path, :method_to_instrument, :instrumenting_module
|
17
17
|
|
18
|
-
def initialize module_name, instrumentation_file_path, method_to_instrument: nil, instrumenting_module:
|
18
|
+
def initialize module_name, instrumentation_file_path, method_to_instrument: nil, instrumenting_module: nil
|
19
19
|
@applied = false
|
20
20
|
@module_name = module_name
|
21
21
|
@method_to_instrument = method_to_instrument
|
@@ -71,10 +71,10 @@ module Contrast
|
|
71
71
|
|
72
72
|
def module_lookup
|
73
73
|
@_module_lookup ||= begin
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
74
|
+
Module.cs__const_get module_name
|
75
|
+
rescue StandardError => _e
|
76
|
+
nil
|
77
|
+
end
|
78
78
|
end
|
79
79
|
end
|
80
80
|
end
|
@@ -30,20 +30,20 @@ module Contrast
|
|
30
30
|
# extensions.
|
31
31
|
def apply_direct_patches!
|
32
32
|
@_apply_direct_patches ||= begin
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
33
|
+
Contrast::Extension::Assess::ArrayPropagator.instrument_array_track
|
34
|
+
Contrast::Extension::Assess::EvalTrigger.instrument_basic_object_track
|
35
|
+
Contrast::Extension::Assess::EvalTrigger.instrument_module_track
|
36
|
+
Contrast::Extension::Assess::FiberPropagator.instrument_fiber_track
|
37
|
+
Contrast::Extension::Assess::HashPropagator.instrument_hash_track
|
38
|
+
Contrast::Extension::Assess::KernelPropagator.instrument_kernel_track
|
39
|
+
Contrast::Extension::Assess::MarshalPropagator.instrument_marshal_load
|
40
|
+
Contrast::Extension::Assess::RegexpPropagator.instrument_regexp_track
|
41
|
+
Contrast::Extension::Assess::StringPropagator.instrument_string
|
42
|
+
Contrast::Extension::Assess::StringPropagator.instrument_string_interpolation
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
Contrast::Extension::Protect::Kernel.instrument
|
45
|
+
true
|
46
|
+
end
|
47
47
|
end
|
48
48
|
|
49
49
|
def apply_load_patches!
|
@@ -65,13 +65,13 @@ module Contrast
|
|
65
65
|
# handling
|
66
66
|
def apply_require_patches!
|
67
67
|
@_apply_require_patches ||= begin
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
68
|
+
require 'contrast/extension/thread'
|
69
|
+
require 'contrast/extension/kernel'
|
70
|
+
true
|
71
|
+
rescue LoadError, StandardError => e
|
72
|
+
logger.error('failed instrumenting apply_require_patches!', e)
|
73
|
+
false
|
74
|
+
end
|
75
75
|
end
|
76
76
|
|
77
77
|
def after_load_patches
|
@@ -61,16 +61,16 @@ module Contrast
|
|
61
61
|
# @return [Integer] count of methods to be patched
|
62
62
|
def num_expected_patches
|
63
63
|
@_num_expected_patches ||= begin
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
64
|
+
instance_methods = Set.new
|
65
|
+
singleton_methods = Set.new
|
66
|
+
sort_method_names(source_nodes, instance_methods, singleton_methods)
|
67
|
+
sort_method_names(propagator_nodes, instance_methods, singleton_methods)
|
68
|
+
sort_method_names(trigger_nodes, instance_methods, singleton_methods)
|
69
|
+
sort_method_names(inventory_nodes, instance_methods, singleton_methods)
|
70
|
+
sort_method_names(protect_nodes, instance_methods, singleton_methods)
|
71
|
+
sort_method_names(deadzone_nodes, instance_methods, singleton_methods)
|
72
|
+
instance_methods.length + singleton_methods.length
|
73
|
+
end
|
74
74
|
end
|
75
75
|
|
76
76
|
private
|
@@ -124,12 +124,26 @@ module Contrast
|
|
124
124
|
end
|
125
125
|
|
126
126
|
def find_source_node class_name, method_name, instance_method
|
127
|
-
sources.find
|
127
|
+
sources.find do |source|
|
128
|
+
source.class_name == class_name &&
|
129
|
+
source.method_name == method_name &&
|
130
|
+
source.instance_method == instance_method
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def find_propagator_node class_name, method_name, instance_method
|
135
|
+
propagators.find do |propagator|
|
136
|
+
propagator.class_name == class_name &&
|
137
|
+
propagator.method_name == method_name &&
|
138
|
+
propagator.instance_method == instance_method
|
139
|
+
end
|
128
140
|
end
|
129
141
|
|
130
142
|
def find_node rule_id, class_name, method_name, instance_method
|
131
143
|
find_triggers_by_rule(rule_id).find do |node|
|
132
|
-
node.class_name == class_name &&
|
144
|
+
node.class_name == class_name &&
|
145
|
+
node.method_name == method_name &&
|
146
|
+
node.instance_method == instance_method
|
133
147
|
end
|
134
148
|
end
|
135
149
|
end
|
@@ -30,11 +30,9 @@ module Contrast
|
|
30
30
|
Contrast::Agent::Protect::Policy::AppliesDeserializationRule.apply_deserialization_command_check(command)
|
31
31
|
return if skip_analysis?
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, class_name, method, command)
|
37
|
-
end
|
33
|
+
clazz = object.is_a?(Module) ? object : object.cs__class
|
34
|
+
class_name = clazz.cs__name
|
35
|
+
rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, class_name, method, command)
|
38
36
|
end
|
39
37
|
|
40
38
|
protected
|
@@ -27,7 +27,7 @@ module Contrast
|
|
27
27
|
def apply_rule__io method, _exception, _properties, object, args
|
28
28
|
need_rewind = false
|
29
29
|
potential_xml = args[0]
|
30
|
-
return unless potential_xml
|
30
|
+
return unless potential_xml.cs__respond_to?(:rewind)
|
31
31
|
|
32
32
|
xml = potential_xml.read
|
33
33
|
need_rewind = true
|
@@ -46,42 +46,42 @@ module Contrast
|
|
46
46
|
# Should also handle the ;jsessionid.
|
47
47
|
def normalized_uri
|
48
48
|
@_normalized_uri ||= begin
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
49
|
+
path = rack_request.path
|
50
|
+
uri = path.split(Contrast::Utils::ObjectShare::SEMICOLON)[0] # remove ;jsessionid
|
51
|
+
uri = uri.split(Contrast::Utils::ObjectShare::QUESTION_MARK)[0] # remove ?query_string=
|
52
|
+
uri.gsub(INNER_REST_TOKEN, INNER_NUMBER_MARKER) # replace interior tokens
|
53
|
+
uri.gsub(LAST_REST_TOKEN, LAST_NUMBER_MARKER) # replace last token
|
54
|
+
end
|
55
55
|
end
|
56
56
|
|
57
57
|
def document_type
|
58
58
|
@_document_type ||= begin
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
59
|
+
if /xml/i.match?(content_type) || body&.start_with?('<?xml')
|
60
|
+
:XML
|
61
|
+
elsif /json/i.match?(content_type) || body&.match?(/\s*[{\[]/)
|
62
|
+
:JSON
|
63
|
+
else
|
64
|
+
:NORMAL
|
65
|
+
end
|
66
|
+
end
|
67
67
|
end
|
68
68
|
|
69
69
|
# Header keys upcased and any underscores replaced with dashes
|
70
70
|
def headers
|
71
71
|
@_headers ||= begin
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
72
|
+
with_contrast_scope do
|
73
|
+
hash = {}
|
74
|
+
env.each do |key, value|
|
75
|
+
next unless key
|
76
|
+
|
77
|
+
name = key.to_s
|
78
|
+
next unless name.start_with?(Contrast::Utils::ObjectShare::HTTP_SCORE)
|
79
|
+
|
80
|
+
hash[Contrast::Utils::StringUtils.normalized_key(name)] = value
|
81
|
+
end
|
82
|
+
hash
|
83
|
+
end
|
84
|
+
end
|
85
85
|
end
|
86
86
|
|
87
87
|
def body
|
@@ -121,13 +121,13 @@ module Contrast
|
|
121
121
|
|
122
122
|
def file_names
|
123
123
|
@_file_names ||= begin
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
124
|
+
names = {}
|
125
|
+
parsed_data = Rack::Multipart.parse_multipart(rack_request.env)
|
126
|
+
traverse_parsed_multipart(parsed_data, names)
|
127
|
+
rescue StandardError => _e
|
128
|
+
logger.warn('Unable to parse multipart request!')
|
129
|
+
{}
|
130
|
+
end
|
131
131
|
end
|
132
132
|
|
133
133
|
def hash_id
|