rack-sanitizer 2.0.3 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +12 -5
- data/.github/workflows/release.yml +24 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +12 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +38 -7
- data/LICENSE.txt +2 -1
- data/README.md +1 -1
- data/Rakefile +4 -2
- data/bin/bacon +488 -0
- data/bin/rubocop +27 -0
- data/dev.yml +17 -0
- data/gemfiles/rack_2.2.gemfile +9 -0
- data/gemfiles/rack_3.0.gemfile +9 -0
- data/gemfiles/rack_3.1.gemfile +9 -0
- data/gemfiles/rack_3.2.gemfile +9 -0
- data/lib/rack/sanitizer.rb +34 -27
- data/rack-sanitizer.gemspec +6 -11
- data/test/test_sanitizer.rb +149 -69
- metadata +15 -51
data/test/test_sanitizer.rb
CHANGED
|
@@ -1,27 +1,70 @@
|
|
|
1
|
-
# encoding:ascii-8bit
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
|
-
require 'bacon/colored_output'
|
|
4
4
|
require 'cgi'
|
|
5
|
+
require 'rack/lint'
|
|
6
|
+
require 'rack/mock'
|
|
5
7
|
require 'rack/sanitizer'
|
|
6
8
|
|
|
7
9
|
describe Rack::Sanitizer do
|
|
10
|
+
class ResponseBody < String
|
|
11
|
+
attr_reader :env
|
|
12
|
+
|
|
13
|
+
def initialize(env)
|
|
14
|
+
super('')
|
|
15
|
+
@env = env
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
8
19
|
before do
|
|
9
|
-
@
|
|
20
|
+
@build_app = ->(options = {}) do
|
|
21
|
+
linted_app = Rack::Lint.new(->env { [200, {}, [ResponseBody.new(env)]] })
|
|
22
|
+
Rack::Sanitizer.new(->env { linted_app.(env) }, options)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@rack_env = ->(env) do
|
|
26
|
+
Rack::MockRequest.env_for('/').tap do |request_env|
|
|
27
|
+
request_env.delete('CONTENT_LENGTH')
|
|
28
|
+
request_env.merge!(env.reject { |_key, value| value.nil? })
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@call_app = ->(env) do
|
|
33
|
+
response = @app.(@rack_env.call(env))
|
|
34
|
+
response_body = nil
|
|
35
|
+
response[2].each do |chunk|
|
|
36
|
+
response_body = chunk
|
|
37
|
+
end
|
|
38
|
+
response[2].close
|
|
39
|
+
response_body.env
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
@app = @build_app.call
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "conforms to Rack::Lint" do
|
|
46
|
+
app = Rack::Lint.new(Rack::Sanitizer.new(->(_env) { [200, {}, ["OK"]] }))
|
|
47
|
+
status, headers, body = app.(Rack::MockRequest.env_for('/'))
|
|
48
|
+
|
|
49
|
+
status.should == 200
|
|
50
|
+
headers.should == {}
|
|
51
|
+
body.each { |chunk| chunk.should == "OK" }
|
|
52
|
+
body.close
|
|
10
53
|
end
|
|
11
54
|
|
|
12
55
|
shared :does_sanitize_plain do
|
|
13
56
|
it "sanitizes plaintext entity (HTTP_USER_AGENT)" do
|
|
14
|
-
env = @
|
|
57
|
+
env = @call_app.call({ "HTTP_USER_AGENT" => @plain_input })
|
|
15
58
|
result = env["HTTP_USER_AGENT"]
|
|
16
59
|
|
|
17
|
-
result.encoding.should == Encoding::
|
|
60
|
+
result.encoding.should == Encoding::ASCII_8BIT
|
|
18
61
|
result.should.be.valid_encoding
|
|
19
62
|
end
|
|
20
63
|
end
|
|
21
64
|
|
|
22
65
|
shared :does_sanitize_uri do
|
|
23
66
|
it "sanitizes URI-like entity (REQUEST_PATH)" do
|
|
24
|
-
env = @
|
|
67
|
+
env = @call_app.call({ "REQUEST_PATH" => @uri_input })
|
|
25
68
|
result = env["REQUEST_PATH"]
|
|
26
69
|
|
|
27
70
|
result.encoding.should == Encoding::US_ASCII
|
|
@@ -31,8 +74,8 @@ describe Rack::Sanitizer do
|
|
|
31
74
|
|
|
32
75
|
describe "with invalid host input" do
|
|
33
76
|
it "sanitizes host entity (SERVER_NAME)" do
|
|
34
|
-
host = "host\xD0".force_encoding(
|
|
35
|
-
env = @
|
|
77
|
+
host = "host\xD0".dup.force_encoding(Encoding::UTF_8)
|
|
78
|
+
env = @call_app.call({ "SERVER_NAME" => host })
|
|
36
79
|
result = env["SERVER_NAME"]
|
|
37
80
|
|
|
38
81
|
result.encoding.should == Encoding::US_ASCII
|
|
@@ -42,8 +85,8 @@ describe Rack::Sanitizer do
|
|
|
42
85
|
|
|
43
86
|
describe "with invalid UTF-8 input" do
|
|
44
87
|
before do
|
|
45
|
-
@plain_input = "foo\xe0".force_encoding(
|
|
46
|
-
@uri_input = "http://bar/foo%E0".force_encoding(
|
|
88
|
+
@plain_input = "foo\xe0".dup.force_encoding(Encoding::UTF_8)
|
|
89
|
+
@uri_input = "http://bar/foo%E0".dup.force_encoding(Encoding::UTF_8)
|
|
47
90
|
end
|
|
48
91
|
|
|
49
92
|
behaves_like :does_sanitize_plain
|
|
@@ -52,7 +95,7 @@ describe Rack::Sanitizer do
|
|
|
52
95
|
|
|
53
96
|
describe "with invalid, incorrectly percent-encoded UTF-8 URI input" do
|
|
54
97
|
before do
|
|
55
|
-
@uri_input = "http://bar/foo%E0\xe0".force_encoding(
|
|
98
|
+
@uri_input = "http://bar/foo%E0\xe0".dup.force_encoding(Encoding::UTF_8)
|
|
56
99
|
end
|
|
57
100
|
|
|
58
101
|
behaves_like :does_sanitize_uri
|
|
@@ -78,18 +121,18 @@ describe Rack::Sanitizer do
|
|
|
78
121
|
|
|
79
122
|
shared :identity_plain do
|
|
80
123
|
it "does not change plaintext entity (HTTP_USER_AGENT)" do
|
|
81
|
-
env = @
|
|
124
|
+
env = @call_app.call({ "HTTP_USER_AGENT" => @plain_input })
|
|
82
125
|
result = env["HTTP_USER_AGENT"]
|
|
83
126
|
|
|
84
|
-
result.encoding.should == Encoding::UTF_8
|
|
127
|
+
result.encoding.should == (@plain_input.ascii_only? ? Encoding::UTF_8 : Encoding::ASCII_8BIT)
|
|
85
128
|
result.should.be.valid_encoding
|
|
86
|
-
result.should == @plain_input
|
|
129
|
+
result.bytes.should == @plain_input.bytes
|
|
87
130
|
end
|
|
88
131
|
end
|
|
89
132
|
|
|
90
133
|
shared :identity_uri do
|
|
91
134
|
it "does not change URI-like entity (REQUEST_PATH)" do
|
|
92
|
-
env = @
|
|
135
|
+
env = @call_app.call({ "REQUEST_PATH" => @uri_input })
|
|
93
136
|
result = env["REQUEST_PATH"]
|
|
94
137
|
|
|
95
138
|
result.encoding.should == Encoding::US_ASCII
|
|
@@ -100,8 +143,8 @@ describe Rack::Sanitizer do
|
|
|
100
143
|
|
|
101
144
|
describe "with valid UTF-8 input" do
|
|
102
145
|
before do
|
|
103
|
-
@plain_input = "foo bar лол".force_encoding(
|
|
104
|
-
@uri_input = "http://bar/foo+bar+%D0%BB%D0%BE%D0%BB".force_encoding(
|
|
146
|
+
@plain_input = "foo bar лол".dup.force_encoding(Encoding::UTF_8)
|
|
147
|
+
@uri_input = "http://bar/foo+bar+%D0%BB%D0%BE%D0%BB".dup.force_encoding(Encoding::UTF_8)
|
|
105
148
|
end
|
|
106
149
|
|
|
107
150
|
behaves_like :identity_plain
|
|
@@ -109,7 +152,7 @@ describe Rack::Sanitizer do
|
|
|
109
152
|
|
|
110
153
|
describe "with URI characters from reserved range" do
|
|
111
154
|
before do
|
|
112
|
-
@uri_input = "http://bar/foo+%2F%3A+bar+%D0%BB%D0%BE%D0%BB".force_encoding(
|
|
155
|
+
@uri_input = "http://bar/foo+%2F%3A+bar+%D0%BB%D0%BE%D0%BB".dup.force_encoding(Encoding::UTF_8)
|
|
113
156
|
end
|
|
114
157
|
|
|
115
158
|
behaves_like :identity_uri
|
|
@@ -118,12 +161,12 @@ describe Rack::Sanitizer do
|
|
|
118
161
|
|
|
119
162
|
describe "with valid, not percent-encoded UTF-8 URI input" do
|
|
120
163
|
before do
|
|
121
|
-
@uri_input = "http://bar/foo+bar+лол".force_encoding(
|
|
164
|
+
@uri_input = "http://bar/foo+bar+лол".dup.force_encoding(Encoding::UTF_8)
|
|
122
165
|
@encoded = "http://bar/foo+bar+#{CGI.escape("лол")}"
|
|
123
166
|
end
|
|
124
167
|
|
|
125
168
|
it "does not change URI-like entity (REQUEST_PATH)" do
|
|
126
|
-
env = @
|
|
169
|
+
env = @call_app.call({ "REQUEST_PATH" => @uri_input })
|
|
127
170
|
result = env["REQUEST_PATH"]
|
|
128
171
|
|
|
129
172
|
result.encoding.should == Encoding::US_ASCII
|
|
@@ -152,13 +195,13 @@ describe Rack::Sanitizer do
|
|
|
152
195
|
|
|
153
196
|
describe "with frozen strings" do
|
|
154
197
|
before do
|
|
155
|
-
@plain_input = "bar baz"
|
|
156
|
-
@uri_input = "http://bar/bar+baz"
|
|
198
|
+
@plain_input = "bar baz"
|
|
199
|
+
@uri_input = "http://bar/bar+baz"
|
|
157
200
|
end
|
|
158
201
|
|
|
159
202
|
it "preserves the frozen? status of input" do
|
|
160
|
-
env = @
|
|
161
|
-
|
|
203
|
+
env = @call_app.call({ "HTTP_USER_AGENT" => @plain_input,
|
|
204
|
+
"REQUEST_PATH" => @uri_input })
|
|
162
205
|
|
|
163
206
|
env["HTTP_USER_AGENT"].should.be.frozen
|
|
164
207
|
env["REQUEST_PATH"].should.be.frozen
|
|
@@ -167,12 +210,12 @@ describe Rack::Sanitizer do
|
|
|
167
210
|
|
|
168
211
|
describe "with symbols in the env" do
|
|
169
212
|
before do
|
|
170
|
-
@uri_input = "http://bar/foo%E0\xe0".force_encoding(
|
|
213
|
+
@uri_input = "http://bar/foo%E0\xe0".dup.force_encoding(Encoding::UTF_8)
|
|
171
214
|
end
|
|
172
215
|
|
|
173
216
|
it "sanitizes REQUEST_PATH with invalid UTF-8 URI input" do
|
|
174
|
-
env
|
|
175
|
-
|
|
217
|
+
env = @call_app.call({ "requested_at" => "2014-07-22",
|
|
218
|
+
"REQUEST_PATH" => @uri_input })
|
|
176
219
|
|
|
177
220
|
result = env["REQUEST_PATH"]
|
|
178
221
|
|
|
@@ -183,7 +226,7 @@ describe Rack::Sanitizer do
|
|
|
183
226
|
|
|
184
227
|
describe "with form data" do
|
|
185
228
|
def request_env
|
|
186
|
-
@plain_input = "foo bar лол".force_encoding(
|
|
229
|
+
@plain_input = "foo bar лол".dup.force_encoding(Encoding::UTF_8)
|
|
187
230
|
{
|
|
188
231
|
"REQUEST_METHOD" => "POST",
|
|
189
232
|
"CONTENT_TYPE" => "application/x-www-form-urlencoded;foo=bar",
|
|
@@ -193,18 +236,16 @@ describe Rack::Sanitizer do
|
|
|
193
236
|
end
|
|
194
237
|
|
|
195
238
|
def sanitize_form_data(request_env = request_env())
|
|
196
|
-
@uri_input = "http://bar/foo+%2F%3A+bar+%D0%BB%D0%BE%D0%BB".force_encoding(
|
|
197
|
-
@response_env = @
|
|
239
|
+
@uri_input = "http://bar/foo+%2F%3A+bar+%D0%BB%D0%BE%D0%BB".dup.force_encoding(Encoding::UTF_8)
|
|
240
|
+
@response_env = @call_app.call(request_env)
|
|
198
241
|
sanitized_input = @response_env['rack.input'].read
|
|
199
242
|
|
|
200
243
|
yield sanitized_input if block_given?
|
|
201
244
|
|
|
202
|
-
@response_env['rack.input'].rewind
|
|
203
245
|
behaves_like :does_sanitize_plain
|
|
204
246
|
behaves_like :does_sanitize_uri
|
|
205
247
|
behaves_like :identity_plain
|
|
206
248
|
behaves_like :identity_uri
|
|
207
|
-
@response_env['rack.input'].close
|
|
208
249
|
end
|
|
209
250
|
|
|
210
251
|
class BrokenIO < StringIO
|
|
@@ -214,9 +255,18 @@ describe Rack::Sanitizer do
|
|
|
214
255
|
end
|
|
215
256
|
|
|
216
257
|
it "returns HTTP 400 on EOF" do
|
|
217
|
-
@rack_input = BrokenIO.new
|
|
218
|
-
|
|
219
|
-
|
|
258
|
+
@rack_input = BrokenIO.new("".b)
|
|
259
|
+
app = Rack::Lint.new(Rack::Sanitizer.new(->env {
|
|
260
|
+
env['rack.input'].read
|
|
261
|
+
[200, {}, ["OK"]]
|
|
262
|
+
}))
|
|
263
|
+
env = @rack_env.call(request_env.merge("HTTP_USER_AGENT" => "foo"))
|
|
264
|
+
status, headers, body = app.(env)
|
|
265
|
+
response_body = []
|
|
266
|
+
body.each { |chunk| response_body << chunk }
|
|
267
|
+
body.close
|
|
268
|
+
|
|
269
|
+
[status, headers, response_body].should == [400, { "content-type" => "text/plain" }, ["Bad Request"]]
|
|
220
270
|
end
|
|
221
271
|
|
|
222
272
|
it "sanitizes StringIO rack.input" do
|
|
@@ -242,7 +292,7 @@ describe Rack::Sanitizer do
|
|
|
242
292
|
end
|
|
243
293
|
|
|
244
294
|
it "sanitizes StringIO rack.input with bad encoding" do
|
|
245
|
-
input =
|
|
295
|
+
input = "foo=bla&quux=bar\xED"
|
|
246
296
|
@rack_input = StringIO.new input
|
|
247
297
|
|
|
248
298
|
sanitize_form_data do |sanitized_input|
|
|
@@ -293,7 +343,7 @@ describe Rack::Sanitizer do
|
|
|
293
343
|
|
|
294
344
|
it "sanitizes non-StringIO rack.input with bad encoding" do
|
|
295
345
|
require 'rack/rewindable_input'
|
|
296
|
-
input =
|
|
346
|
+
input = "foo=bla&quux=bar\xED"
|
|
297
347
|
@rack_input = Rack::RewindableInput.new(StringIO.new(input))
|
|
298
348
|
|
|
299
349
|
sanitize_form_data do |sanitized_input|
|
|
@@ -304,7 +354,7 @@ describe Rack::Sanitizer do
|
|
|
304
354
|
end
|
|
305
355
|
|
|
306
356
|
it "does not sanitize the rack body if there is no CONTENT_TYPE" do
|
|
307
|
-
input =
|
|
357
|
+
input = "foo=bla&quux=bar\xED"
|
|
308
358
|
@rack_input = StringIO.new input
|
|
309
359
|
|
|
310
360
|
env = request_env.update('CONTENT_TYPE' => nil)
|
|
@@ -316,7 +366,7 @@ describe Rack::Sanitizer do
|
|
|
316
366
|
end
|
|
317
367
|
|
|
318
368
|
it "does not sanitize the rack body if there is empty CONTENT_TYPE" do
|
|
319
|
-
input =
|
|
369
|
+
input = "foo=bla&quux=bar\xED"
|
|
320
370
|
@rack_input = StringIO.new input
|
|
321
371
|
|
|
322
372
|
env = request_env.update('CONTENT_TYPE' => '')
|
|
@@ -328,10 +378,10 @@ describe Rack::Sanitizer do
|
|
|
328
378
|
end
|
|
329
379
|
|
|
330
380
|
it "adjusts content-length when replacing input" do
|
|
331
|
-
input =
|
|
381
|
+
input = "foo=bla&quux=bar\xED"
|
|
332
382
|
@rack_input = StringIO.new input
|
|
333
383
|
|
|
334
|
-
env = request_env.update("CONTENT_LENGTH" => input.bytesize)
|
|
384
|
+
env = request_env.update("CONTENT_LENGTH" => input.bytesize.to_s)
|
|
335
385
|
sanitize_form_data(env) do |sanitized_input|
|
|
336
386
|
sanitized_input.bytesize.should != input.bytesize
|
|
337
387
|
@response_env["CONTENT_LENGTH"].should == sanitized_input.bytesize.to_s
|
|
@@ -339,7 +389,7 @@ describe Rack::Sanitizer do
|
|
|
339
389
|
end
|
|
340
390
|
|
|
341
391
|
it "does not sanitize null bytes by default" do
|
|
342
|
-
input =
|
|
392
|
+
input = "foo=bla&quux=bar%00"
|
|
343
393
|
@rack_input = StringIO.new input
|
|
344
394
|
|
|
345
395
|
sanitize_form_data do |sanitized_input|
|
|
@@ -362,25 +412,31 @@ describe Rack::Sanitizer do
|
|
|
362
412
|
|
|
363
413
|
it "sanitizes bad http cookie" do
|
|
364
414
|
@cookie = "foo=bla; quux=bar\xED"
|
|
365
|
-
response_env = @
|
|
415
|
+
response_env = @call_app.call(request_env)
|
|
366
416
|
response_env['HTTP_COOKIE'].should != @cookie
|
|
367
417
|
response_env['HTTP_COOKIE'].should == 'foo=bla; quux=bar%EF%BF%BD'
|
|
368
418
|
end
|
|
369
419
|
|
|
420
|
+
it "preserves commas in http cookie values" do
|
|
421
|
+
@cookie = "foo=bar,baz; quux=qux"
|
|
422
|
+
response_env = @call_app.call(request_env)
|
|
423
|
+
response_env['HTTP_COOKIE'].should == @cookie
|
|
424
|
+
end
|
|
425
|
+
|
|
370
426
|
it "does not change ok http cookie" do
|
|
371
427
|
@cookie = "foo=bla; quux=bar"
|
|
372
|
-
response_env = @
|
|
428
|
+
response_env = @call_app.call(request_env)
|
|
373
429
|
response_env['HTTP_COOKIE'].should == @cookie
|
|
374
430
|
|
|
375
431
|
@cookie = "foo=b%3bla; quux=b%20a%20r"
|
|
376
|
-
response_env = @
|
|
432
|
+
response_env = @call_app.call(request_env)
|
|
377
433
|
response_env['HTTP_COOKIE'].should == @cookie
|
|
378
434
|
end
|
|
379
435
|
end
|
|
380
436
|
|
|
381
437
|
describe "with custom content-type" do
|
|
382
438
|
def request_env
|
|
383
|
-
@plain_input = "foo bar лол".force_encoding(
|
|
439
|
+
@plain_input = "foo bar лол".dup.force_encoding(Encoding::UTF_8)
|
|
384
440
|
{
|
|
385
441
|
"REQUEST_METHOD" => "POST",
|
|
386
442
|
"CONTENT_TYPE" => "application/vnd.api+json",
|
|
@@ -390,15 +446,15 @@ describe Rack::Sanitizer do
|
|
|
390
446
|
end
|
|
391
447
|
|
|
392
448
|
def sanitize_data(request_env = request_env())
|
|
393
|
-
@uri_input = "http://bar/foo+%2F%3A+bar+%D0%BB%D0%BE%D0%BB".force_encoding(
|
|
394
|
-
@response_env = @
|
|
449
|
+
@uri_input = "http://bar/foo+%2F%3A+bar+%D0%BB%D0%BE%D0%BB".dup.force_encoding(Encoding::UTF_8)
|
|
450
|
+
@response_env = @call_app.call(request_env)
|
|
395
451
|
sanitized_input = @response_env['rack.input'].read
|
|
396
452
|
|
|
397
453
|
yield sanitized_input if block_given?
|
|
398
454
|
end
|
|
399
455
|
|
|
400
456
|
it "does not sanitize custom content-type by default" do
|
|
401
|
-
input =
|
|
457
|
+
input = "foo=bla&quux=bar\xED"
|
|
402
458
|
@rack_input = StringIO.new input
|
|
403
459
|
|
|
404
460
|
env = request_env
|
|
@@ -410,47 +466,47 @@ describe Rack::Sanitizer do
|
|
|
410
466
|
end
|
|
411
467
|
|
|
412
468
|
it "sanitizes custom content-type if additional_content_types given" do
|
|
413
|
-
@app =
|
|
414
|
-
input =
|
|
469
|
+
@app = @build_app.call(additional_content_types: ["application/vnd.api+json"])
|
|
470
|
+
input = "foo=bla&quux=bar\xED"
|
|
415
471
|
@rack_input = StringIO.new input
|
|
416
472
|
|
|
417
473
|
env = request_env
|
|
418
474
|
sanitize_data(env) do |sanitized_input|
|
|
419
|
-
sanitized_input.encoding.should == Encoding::
|
|
475
|
+
sanitized_input.encoding.should == Encoding::ASCII_8BIT
|
|
420
476
|
sanitized_input.should.be.valid_encoding
|
|
421
477
|
sanitized_input.should != input
|
|
422
478
|
end
|
|
423
479
|
end
|
|
424
480
|
|
|
425
481
|
it "sanitizes default content-type if additional_content_types given" do
|
|
426
|
-
@app =
|
|
427
|
-
input =
|
|
482
|
+
@app = @build_app.call(additional_content_types: ["application/vnd.api+json"])
|
|
483
|
+
input = "foo=bla&quux=bar\xED"
|
|
428
484
|
@rack_input = StringIO.new input
|
|
429
485
|
|
|
430
486
|
env = request_env.update('CONTENT_TYPE' => 'application/json')
|
|
431
487
|
sanitize_data(env) do |sanitized_input|
|
|
432
|
-
sanitized_input.encoding.should == Encoding::
|
|
488
|
+
sanitized_input.encoding.should == Encoding::ASCII_8BIT
|
|
433
489
|
sanitized_input.should.be.valid_encoding
|
|
434
490
|
sanitized_input.should != input
|
|
435
491
|
end
|
|
436
492
|
end
|
|
437
493
|
|
|
438
494
|
it "sanitizes custom content-type if sanitizable_content_types given" do
|
|
439
|
-
@app =
|
|
440
|
-
input =
|
|
495
|
+
@app = @build_app.call(sanitizable_content_types: ["application/vnd.api+json"])
|
|
496
|
+
input = "foo=bla&quux=bar\xED"
|
|
441
497
|
@rack_input = StringIO.new input
|
|
442
498
|
|
|
443
499
|
env = request_env
|
|
444
500
|
sanitize_data(env) do |sanitized_input|
|
|
445
|
-
sanitized_input.encoding.should == Encoding::
|
|
501
|
+
sanitized_input.encoding.should == Encoding::ASCII_8BIT
|
|
446
502
|
sanitized_input.should.be.valid_encoding
|
|
447
503
|
sanitized_input.should != input
|
|
448
504
|
end
|
|
449
505
|
end
|
|
450
506
|
|
|
451
507
|
it "does not sanitize default content-type if sanitizable_content_types does not include it" do
|
|
452
|
-
@app =
|
|
453
|
-
input =
|
|
508
|
+
@app = @build_app.call(sanitizable_content_types: ["application/vnd.api+json"])
|
|
509
|
+
input = "foo=bla&quux=bar\xED"
|
|
454
510
|
@rack_input = StringIO.new input
|
|
455
511
|
|
|
456
512
|
env = request_env.update('CONTENT_TYPE' => 'application/json')
|
|
@@ -464,7 +520,7 @@ describe Rack::Sanitizer do
|
|
|
464
520
|
|
|
465
521
|
describe "with custom strategy" do
|
|
466
522
|
def request_env
|
|
467
|
-
@plain_input = "foo bar лол".force_encoding(
|
|
523
|
+
@plain_input = "foo bar лол".dup.force_encoding(Encoding::UTF_8)
|
|
468
524
|
{
|
|
469
525
|
"REQUEST_METHOD" => "POST",
|
|
470
526
|
"CONTENT_TYPE" => "application/json",
|
|
@@ -474,29 +530,29 @@ describe Rack::Sanitizer do
|
|
|
474
530
|
end
|
|
475
531
|
|
|
476
532
|
def sanitize_data(request_env = request_env())
|
|
477
|
-
@uri_input = "http://bar/foo+%2F%3A+bar+%D0%BB%D0%BE%D0%BB".force_encoding(
|
|
478
|
-
@response_env = @
|
|
533
|
+
@uri_input = "http://bar/foo+%2F%3A+bar+%D0%BB%D0%BE%D0%BB".dup.force_encoding(Encoding::UTF_8)
|
|
534
|
+
@response_env = @call_app.call(request_env)
|
|
479
535
|
sanitized_input = @response_env['rack.input'].read
|
|
480
536
|
|
|
481
537
|
yield sanitized_input if block_given?
|
|
482
538
|
end
|
|
483
539
|
|
|
484
540
|
it "calls a default strategy (replace)" do
|
|
485
|
-
@app =
|
|
541
|
+
@app = @build_app.call
|
|
486
542
|
|
|
487
543
|
input = "foo=bla&quux=bar\xED"
|
|
488
544
|
@rack_input = StringIO.new input
|
|
489
545
|
|
|
490
546
|
env = request_env
|
|
491
547
|
sanitize_data(env) do |sanitized_input|
|
|
492
|
-
sanitized_input.encoding.should == Encoding::
|
|
548
|
+
sanitized_input.encoding.should == Encoding::ASCII_8BIT
|
|
493
549
|
sanitized_input.should.be.valid_encoding
|
|
494
550
|
sanitized_input.should != input
|
|
495
551
|
end
|
|
496
552
|
end
|
|
497
553
|
|
|
498
554
|
it "calls the exception strategy" do
|
|
499
|
-
@app =
|
|
555
|
+
@app = @build_app.call(strategy: :exception)
|
|
500
556
|
|
|
501
557
|
input = "foo=bla&quux=bar\xED"
|
|
502
558
|
@rack_input = StringIO.new input
|
|
@@ -505,12 +561,36 @@ describe Rack::Sanitizer do
|
|
|
505
561
|
should.raise(EncodingError) { sanitize_data(env) }
|
|
506
562
|
end
|
|
507
563
|
|
|
564
|
+
it "returns HTTP 400 before the application reads invalid rack.input" do
|
|
565
|
+
input = "{\"user_session\": {\"invalid\": \"\x80\"}}".b
|
|
566
|
+
@rack_input = StringIO.new(input)
|
|
567
|
+
|
|
568
|
+
sanitizer = Rack::Sanitizer.new(->(_env) { [200, {}, ["OK"]] }, strategy: :exception)
|
|
569
|
+
app = ->(env) do
|
|
570
|
+
begin
|
|
571
|
+
sanitizer.call(env)
|
|
572
|
+
rescue EncodingError
|
|
573
|
+
[400, {}, ["Bad Request"]]
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
app = Rack::Lint.new(app)
|
|
577
|
+
|
|
578
|
+
env = @rack_env.call(request_env.merge("HTTP_USER_AGENT" => "foo"))
|
|
579
|
+
env["CONTENT_LENGTH"] = input.bytesize.to_s
|
|
580
|
+
status, headers, body = app.(env)
|
|
581
|
+
response_body = []
|
|
582
|
+
body.each { |chunk| response_body << chunk }
|
|
583
|
+
body.close
|
|
584
|
+
|
|
585
|
+
[status, headers, response_body].should == [400, {}, ["Bad Request"]]
|
|
586
|
+
end
|
|
587
|
+
|
|
508
588
|
it "accepts a proc as a strategy" do
|
|
509
|
-
truncate = ->
|
|
510
|
-
'replace'.force_encoding(Encoding::UTF_8)
|
|
589
|
+
truncate = ->(input) do
|
|
590
|
+
'replace'.dup.force_encoding(Encoding::UTF_8)
|
|
511
591
|
end
|
|
512
592
|
|
|
513
|
-
@app =
|
|
593
|
+
@app = @build_app.call(strategy: truncate)
|
|
514
594
|
|
|
515
595
|
input = "foo=bla&quux=bar\xED"
|
|
516
596
|
@rack_input = StringIO.new input
|
metadata
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rack-sanitizer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
+
- Shopify - Ruby and Rails Infratructure
|
|
7
8
|
- Jean Boussier
|
|
8
9
|
- whitequark
|
|
9
|
-
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date:
|
|
12
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: rack
|
|
@@ -31,52 +31,10 @@ dependencies:
|
|
|
31
31
|
- - "<"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
33
|
version: '4.0'
|
|
34
|
-
- !ruby/object:Gem::Dependency
|
|
35
|
-
name: bacon
|
|
36
|
-
requirement: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0'
|
|
41
|
-
type: :development
|
|
42
|
-
prerelease: false
|
|
43
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - ">="
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '0'
|
|
48
|
-
- !ruby/object:Gem::Dependency
|
|
49
|
-
name: bacon-colored_output
|
|
50
|
-
requirement: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - ">="
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '0'
|
|
55
|
-
type: :development
|
|
56
|
-
prerelease: false
|
|
57
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - ">="
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '0'
|
|
62
|
-
- !ruby/object:Gem::Dependency
|
|
63
|
-
name: rake
|
|
64
|
-
requirement: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - ">="
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '0'
|
|
69
|
-
type: :development
|
|
70
|
-
prerelease: false
|
|
71
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - ">="
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: '0'
|
|
76
34
|
description: Rack::Sanitizer is a Rack middleware which cleans up invalid UTF8 characters
|
|
77
35
|
in request URI and headers.
|
|
78
36
|
email:
|
|
79
|
-
-
|
|
37
|
+
- ruby-and-rails-infrastructure@shopify.com
|
|
80
38
|
executables: []
|
|
81
39
|
extensions: []
|
|
82
40
|
extra_rdoc_files: []
|
|
@@ -85,13 +43,22 @@ files:
|
|
|
85
43
|
- ".github/dependabot.yml"
|
|
86
44
|
- ".github/workflows/ci.yml"
|
|
87
45
|
- ".github/workflows/cla.yml"
|
|
46
|
+
- ".github/workflows/release.yml"
|
|
88
47
|
- ".gitignore"
|
|
48
|
+
- ".rubocop.yml"
|
|
89
49
|
- ".ruby-version"
|
|
90
50
|
- Gemfile
|
|
91
51
|
- Gemfile.lock
|
|
92
52
|
- LICENSE.txt
|
|
93
53
|
- README.md
|
|
94
54
|
- Rakefile
|
|
55
|
+
- bin/bacon
|
|
56
|
+
- bin/rubocop
|
|
57
|
+
- dev.yml
|
|
58
|
+
- gemfiles/rack_2.2.gemfile
|
|
59
|
+
- gemfiles/rack_3.0.gemfile
|
|
60
|
+
- gemfiles/rack_3.1.gemfile
|
|
61
|
+
- gemfiles/rack_3.2.gemfile
|
|
95
62
|
- lib/rack/sanitizer.rb
|
|
96
63
|
- rack-sanitizer.gemspec
|
|
97
64
|
- test/test_sanitizer.rb
|
|
@@ -100,7 +67,6 @@ licenses:
|
|
|
100
67
|
- MIT
|
|
101
68
|
metadata:
|
|
102
69
|
allowed_push_host: https://rubygems.org/
|
|
103
|
-
post_install_message:
|
|
104
70
|
rdoc_options: []
|
|
105
71
|
require_paths:
|
|
106
72
|
- lib
|
|
@@ -115,9 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
115
81
|
- !ruby/object:Gem::Version
|
|
116
82
|
version: '0'
|
|
117
83
|
requirements: []
|
|
118
|
-
rubygems_version:
|
|
119
|
-
signing_key:
|
|
84
|
+
rubygems_version: 4.0.19
|
|
120
85
|
specification_version: 4
|
|
121
86
|
summary: It is a mordernized and optimized fork of rack-utf8_sanitizer
|
|
122
|
-
test_files:
|
|
123
|
-
- test/test_sanitizer.rb
|
|
87
|
+
test_files: []
|