Skip to content

Commit e1a5f40

Browse files
committed
Make request body reading safe to rewind
1 parent d9034a0 commit e1a5f40

File tree

4 files changed

+104
-0
lines changed

4 files changed

+104
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
```
4646
- Add `config.profiles_sample_interval` to control sampling frequency ([#2745](https://github.com/getsentry/sentry-ruby/pull/2745))
4747
- Both `stackprof` and `vernier` now get sampled at a default frequency of 101 Hz.
48+
- Request body reading checks for `:rewind` to match Rack 3 behavior. ([#2754](https://github.com/getsentry/sentry-ruby/pull/2754))
4849

4950
### Internal
5051

sentry-ruby/lib/sentry/interfaces/request.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ def initialize(env:, send_default_pii:, rack_env_whitelist:)
6969
private
7070

7171
def read_data_from(request)
72+
return "Skipped non-rewindable request body" unless request.body.respond_to?(:rewind)
73+
7274
if request.form_data?
7375
request.POST
7476
elsif request.body # JSON requests, etc

sentry-ruby/spec/sentry/interfaces/request_interface_spec.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,21 @@ def to_s
182182
expect(subject.data).to eq("catch me")
183183
end
184184

185+
it "does not try to read non rewindable body" do
186+
env.merge!(::Rack::RACK_INPUT => double)
187+
188+
expect(subject.data).to eq("Skipped non-rewindable request body")
189+
end
190+
191+
it "reads rewindable body" do
192+
dbl = double
193+
allow(dbl).to receive(:rewind)
194+
allow(dbl).to receive(:read).and_return("stuff")
195+
env.merge!(::Rack::RACK_INPUT => dbl)
196+
197+
expect(subject.data).to eq("stuff")
198+
end
199+
185200
it "stores Authorization header" do
186201
env.merge!("HTTP_AUTHORIZATION" => "Basic YWxhZGRpbjpvcGVuc2VzYW1l")
187202

sentry-ruby/spec/sentry/rack/capture_exceptions_spec.rb

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,92 @@ def inspect
229229
expect(Sentry.get_current_scope.tags).to eq(tag_1: "don't change me")
230230
end
231231
end
232+
233+
context "with send_default_pii" do
234+
before do
235+
perform_basic_setup do |config|
236+
config.send_default_pii = true
237+
end
238+
end
239+
240+
context "with form data" do
241+
let(:additional_headers) do
242+
{ "REQUEST_METHOD" => "POST", ::Rack::RACK_INPUT => StringIO.new("foo=bar") }
243+
end
244+
245+
it "captures the exception with request form data" do
246+
app = ->(_e) { raise exception }
247+
stack = Sentry::Rack::CaptureExceptions.new(app)
248+
249+
expect { stack.call(env) }.to raise_error(ZeroDivisionError)
250+
251+
event = last_sentry_event.to_h
252+
expect(event.dig(:request, :url)).to eq("http://example.org/test")
253+
expect(event.dig(:request, :data)).to eq({ "foo" => "bar" })
254+
end
255+
256+
it "allows later middlewares to read body" do
257+
app = ->(_e) { raise exception }
258+
stack = Sentry::Rack::CaptureExceptions.new(app)
259+
260+
expect { stack.call(env) }.to raise_error(ZeroDivisionError)
261+
expect { ::Rack::Request.new(env).body.read }.not_to raise_error
262+
end
263+
end
264+
265+
context "with rewindable non form data" do
266+
let(:additional_headers) do
267+
{ "REQUEST_METHOD" => "POST", "CONTENT_TYPE" => "application/text", ::Rack::RACK_INPUT => StringIO.new("stuff") }
268+
end
269+
270+
it "captures the exception with request form data" do
271+
app = ->(_e) { raise exception }
272+
stack = Sentry::Rack::CaptureExceptions.new(app)
273+
274+
expect { stack.call(env) }.to raise_error(ZeroDivisionError)
275+
276+
event = last_sentry_event.to_h
277+
expect(event.dig(:request, :url)).to eq("http://example.org/test")
278+
expect(event.dig(:request, :data)).to eq("stuff")
279+
end
280+
281+
it "allows later middlewares to read body" do
282+
app = ->(_e) { raise exception }
283+
stack = Sentry::Rack::CaptureExceptions.new(app)
284+
285+
expect { stack.call(env) }.to raise_error(ZeroDivisionError)
286+
expect { ::Rack::Request.new(env).body.read }.not_to raise_error
287+
end
288+
end
289+
290+
context "with non rewindable non form data" do
291+
let(:dbl) { double }
292+
let(:additional_headers) do
293+
{ "REQUEST_METHOD" => "POST", "CONTENT_TYPE" => "application/text", ::Rack::RACK_INPUT => dbl }
294+
end
295+
296+
it "does not try to read non rewindable body" do
297+
app = ->(_e) { raise exception }
298+
stack = Sentry::Rack::CaptureExceptions.new(app)
299+
300+
expect { stack.call(env) }.to raise_error(ZeroDivisionError)
301+
302+
event = last_sentry_event.to_h
303+
expect(event.dig(:request, :url)).to eq("http://example.org/test")
304+
expect(event.dig(:request, :data)).to eq("Skipped non-rewindable request body")
305+
end
306+
307+
it "allows later middlewares to read body" do
308+
allow(dbl).to receive(:read)
309+
310+
app = ->(_e) { raise exception }
311+
stack = Sentry::Rack::CaptureExceptions.new(app)
312+
313+
expect { stack.call(env) }.to raise_error(ZeroDivisionError)
314+
expect { ::Rack::Request.new(env).body.read }.not_to raise_error
315+
end
316+
end
317+
end
232318
end
233319

234320
describe "performance monitoring" do

0 commit comments

Comments
 (0)