Module: Datadog::AppSec::Contrib::Rack::InputPeeker Private

Defined in:
lib/datadog/appsec/contrib/rack/input_peeker.rb

Overview

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

Peeks at rack.input without changing what downstream Rack code can read

Class Method Summary collapse

Class Method Details

.peek_bytesize(env, limit:) ⇒ Integer?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Peeks at env['rack.input'] and returns the body bytesize when it can be measured within the given limit

NOTE: Over-limit bodies cannot be fully measured, so returning nil is the tradeoff we accept

WARNING: For forward-only input, replaces env['rack.input'] with a replay stream over the peeked bytes, preserving the rest of the original stream

Parameters:

  • env (Hash) —

    Rack environment

  • limit (Integer) —

    Maximum body bytesize to measure

Returns:

  • (Integer, nil) —

    Integer bytesize when measured within limit, including 0; nil when input is missing, over the limit, or cannot be preserved for downstream reads



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/datadog/appsec/contrib/rack/input_peeker.rb', line 32

def peek_bytesize(env, limit:)
  rack_input = env["rack.input"]
  return unless rack_input

  rewindable = rewind? && rack_input.respond_to?(:rewind)

  # NOTE: Rack 2 requires `rack.input` to be rewindable. Rewind before peeking
  #       in case an upstream framework already consumed part of the stream
  return if rewindable && !rewind(rack_input)

  # NOTE: Read one byte past the limit to distinguish an exact-limit body
  #       from an over-limit body without reading the whole stream.
  buffer = Utils::HTTP::BodyReader.read_stream(rack_input, limit: limit)
  over_limit = buffer.bytesize > limit

  if rewindable
    # NOTE: If we cannot rewind after peeking, downstream code would observe
    #       a partially consumed body. Treat it as not safely collectable
    return unless rewind(rack_input)
  else
    env["rack.input"] = if over_limit
      BufferedInput.new(rack_input, buffer: StringIO.new(buffer))
    else
      StringIO.new(buffer)
    end
  end

  over_limit ? nil : buffer.bytesize
end