Class: Datadog::AppSec::Contrib::Rack::Gateway::Request

Inherits:
Instrumentation::Gateway::Argument show all
Defined in:
lib/datadog/appsec/contrib/rack/gateway/request.rb

Overview

Gateway Request argument. Normalized extration of data from Rack::Request

Direct Known Subclasses

Sinatra::Gateway::Request

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(env) ⇒ Request

Returns a new instance of Request.



18
19
20
21
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 18

def initialize(env)
  super()
  @env = env
end

Instance Attribute Details

#envObject (readonly)

Returns the value of attribute env.



16
17
18
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 16

def env
  @env
end

Instance Method Details

#body_bytesize(limit) ⇒ Object

Returns the request body size in bytes using all available methods, or nil when the size cannot be measured within the limit

NOTE: The priority of the measurement is the following: size if it's known, content-length if provided, and buffering to the limit if unknown-length

WARNING: The buffering path adds overhead for streaming web-servers (Rack 3+) when the body length is unknown



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 98

def body_bytesize(limit)
  io = request.body

  return 0 unless io
  return io.size if io.respond_to?(:size)

  # NOTE: {::Rack::Request#content_length} is a plain `CONTENT_LENGTH`
  #       getter forces no body read
  content_length = request.content_length
  return content_length.to_i if content_length

  # NOTE: A parsed-body cache without a raw byte count means the input
  #       stream was consumed before measurement, consider it unknown-length
  return if env.key?("rack.request.form_hash")

  InputPeeker.peek_bytesize(env, limit: limit)
end

#client_ipObject



125
126
127
128
129
130
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 125

def client_ip
  remote_ip = remote_addr
  header_collection = Datadog::Core::HeaderCollection.from_hash(headers)

  Datadog::Tracing::ClientIp.extract_client_ip(header_collection, remote_ip)
end

#collectable_body?Boolean

Whether a request body can be collected without forcing a parse: either form data parseable on demand, or a body already parsed upstream

NOTE: Rack does not parse JSON itself, a body parser middleware such as Rack::JSONBodyParser populates the form hash read by #form_hash

Returns:

  • (Boolean)


121
122
123
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 121

def collectable_body?
  request.form_data? || request.parseable_data? || env.key?("rack.request.form_hash")
end

#cookiesObject



58
59
60
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 58

def cookies
  request.cookies
end

#form_hashObject



74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 74

def form_hash
  # NOTE: Rack populates `rack.request.form_hash` only as a side effect
  #       of {::Rack::Request#POST}, which reads and parses the body.
  request.POST if request.form_data?

  # usually Hash[String, String] but can be a more complex
  # Hash[String, (String|Array|Hash)] when e.g coming from JSON
  env["rack.request.form_hash"]
rescue => e
  Datadog.logger.debug { "AppSec: Failed to parse request body: #{e.class}: #{e.message}" }
  AppSec.telemetry.report(e, description: "AppSec: Failed to parse request body")

  nil
end

#fullpathObject



50
51
52
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 50

def fullpath
  request.fullpath
end

#headersObject



35
36
37
38
39
40
41
42
43
44
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 35

def headers
  result = request.env.each_with_object({}) do |(k, v), h|
    h[k.delete_prefix("HTTP_").tap(&:downcase!).tap { |s| s.tr!("_", "-") }] = v if k.start_with?("HTTP_")
  end

  result["content-type"] = request.content_type if request.content_type
  # Since Rack 3.1, content-length is nil if the body is empty, but we still want to send it to the WAF.
  result["content-length"] = request.content_length || "0"
  result
end

#hostObject



62
63
64
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 62

def host
  request.host
end

#methodObject



31
32
33
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 31

def method
  request.request_method
end

#pathObject



54
55
56
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 54

def path
  request.path
end

#queryObject



27
28
29
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 27

def query
  Utils::HTTP::URLEncoded.parse(request.query_string)
end

#remote_addrObject



70
71
72
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 70

def remote_addr
  env["REMOTE_ADDR"]
end

#requestObject



23
24
25
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 23

def request
  @request ||= ::Rack::Request.new(env)
end

#urlObject



46
47
48
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 46

def url
  request.url
end

#user_agentObject



66
67
68
# File 'lib/datadog/appsec/contrib/rack/gateway/request.rb', line 66

def user_agent
  request.user_agent
end