contrast-agent 3.15.0 → 3.16.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|