Skip to content

Commit e86a547

Browse files
tiegzdavidwinter
andauthored
fix: rescue Rack::Multipart::MultipartPartLimitError and let Sinatra handle it (#161)
* fix: rescue MultipartPartLimitError and let Sinatra handle it. * Add an integration test for show_exceptions:true/false cases * refactor: dry up tests and fix lint issues --------- Co-authored-by: David Winter <[email protected]>
1 parent 7f9e4a0 commit e86a547

File tree

4 files changed

+69
-6
lines changed

4 files changed

+69
-6
lines changed

lib/sensible_logging/middlewares/request_logger.rb

+4
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,9 @@ def call(env) # rubocop:disable Metrics/AbcSize
2828

2929
def filter_params(req)
3030
req.params.reject { |x| @filtered_params.include?(x) }
31+
rescue Rack::Multipart::MultipartPartLimitError
32+
# Let Sinatra handle multipart limit error.
33+
# Don't log the params because we don't have them in this case.
34+
{}
3135
end
3236
end

spec/integration/sensible_logger_spec.rb

+33-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ class App < Sinatra::Base
1616
exclude_params: ['two']
1717
)
1818

19+
error(Rack::Multipart::MultipartPartLimitError) do
20+
halt(413, "You've exceeded the multipart part limit.")
21+
end
22+
1923
get '/hello' do
2024
logger.tagged('todo') do |logger|
2125
logger.debug('test')
@@ -28,10 +32,38 @@ class App < Sinatra::Base
2832
describe 'Sensible Logging integrated with Sinatra' do # rubocop:disable RSpec/DescribeClass
2933
it 'request returns 200' do
3034
env = Rack::MockRequest.env_for('http://www.blah.google.co.uk/hello')
31-
3235
app = App.new
36+
3337
response = app.call(env)
3438

3539
expect(response[0]).to eq(200)
3640
end
41+
42+
context 'when encountering multipart error' do
43+
let(:env) do
44+
data = invalid_multipart_body
45+
46+
Rack::MockRequest.env_for('http://www.blah.google.co.uk/hello', {
47+
'CONTENT_TYPE' => 'multipart/form-data; boundary=myboundary',
48+
'CONTENT_LENGTH' => data.bytesize,
49+
input: StringIO.new(data)
50+
})
51+
end
52+
53+
it 'does not rescue error when show_exceptions is enabled' do
54+
app = App.new
55+
app.settings.show_exceptions = true
56+
57+
expect { app.call(env) }.to raise_error(Rack::Multipart::MultipartPartLimitError)
58+
end
59+
60+
it 'does rescue error when show_exceptions is disabled' do
61+
app = App.new
62+
app.settings.show_exceptions = false
63+
64+
response = app.call(env)
65+
66+
expect(response[0]).to eq(413)
67+
end
68+
end
3769
end

spec/middlewares/request_logger_spec.rb

+21-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
let(:logger) { instance_double(Logger) }
88

99
before do
10-
allow(Time).to receive(:now).and_return(1, 2)
10+
allow(Time).to receive(:now).and_return(
11+
Time.new(2022, 12, 19, 0, 0, 0),
12+
Time.new(2022, 12, 19, 0, 0, 1)
13+
)
1114
allow(logger).to receive(:info)
1215
end
1316

@@ -21,7 +24,7 @@
2124

2225
described_class.new(app).call(env)
2326

24-
expect(logger).to have_received(:info).with('method=GET path=/test client=n/a status=200 duration=1')
27+
expect(logger).to have_received(:info).with('method=GET path=/test client=n/a status=200 duration=1.0')
2528
end
2629

2730
it 'logs the request with REMOTE_ADDR' do
@@ -30,7 +33,7 @@
3033

3134
described_class.new(app).call(env)
3235

33-
expect(logger).to have_received(:info).with('method=GET path=/test client=123.456.789.123 status=200 duration=1')
36+
expect(logger).to have_received(:info).with('method=GET path=/test client=123.456.789.123 status=200 duration=1.0')
3437
end
3538

3639
it 'logs the request with X_FORWARDED_FOR' do
@@ -39,7 +42,7 @@
3942

4043
described_class.new(app).call(env)
4144

42-
expect(logger).to have_received(:info).with('method=GET path=/test client=123.456.789.123 status=200 duration=1')
45+
expect(logger).to have_received(:info).with('method=GET path=/test client=123.456.789.123 status=200 duration=1.0')
4346
end
4447

4548
it 'logs the request with no params if not GET' do
@@ -48,6 +51,19 @@
4851

4952
described_class.new(app).call(env)
5053

51-
expect(logger).to have_received(:info).with('method=POST path=/test client=n/a status=200 duration=1')
54+
expect(logger).to have_received(:info).with('method=POST path=/test client=n/a status=200 duration=1.0')
55+
end
56+
57+
it 'rescues request with no params if Rack::Multipart::MultipartPartLimitError is raised' do # rubocop:disable RSpec/ExampleLength
58+
data = invalid_multipart_body
59+
env = Rack::MockRequest.env_for('http://localhost/test', {
60+
'CONTENT_TYPE' => 'multipart/form-data; boundary=myboundary',
61+
'CONTENT_LENGTH' => data.bytesize,
62+
input: StringIO.new(data)
63+
})
64+
env['logger'] = logger
65+
described_class.new(app).call(env)
66+
67+
expect(logger).to have_received(:info).with('method=GET path=/test client=n/a status=200 duration=1.0')
5268
end
5369
end

spec/spec_helper.rb

+11
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,14 @@
1010
require_relative '../lib/sensible_logging/helpers/subdomain_parser'
1111

1212
ENV['RACK_ENV'] = 'test'
13+
14+
# A multipart form that exceeds rack's multipart form limit.
15+
def invalid_multipart_body
16+
# rubocop:disable Style/StringConcatenation
17+
# rubocop:disable Layout/LineLength
18+
Rack::Utils.multipart_part_limit.times.map do
19+
"--myboundary\r\ncontent-type: text/plain\r\ncontent-disposition: attachment; name=#{SecureRandom.hex(10)}; filename=#{SecureRandom.hex(10)}\r\n\r\ncontents\r\n"
20+
end.join("\r\n") + "--myboundary--\r"
21+
# rubocop:enable Layout/LineLength
22+
# rubocop:enable Style/StringConcatenation
23+
end

0 commit comments

Comments
 (0)