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/bin/rubocop
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
|
12
|
+
|
|
13
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
|
14
|
+
|
|
15
|
+
if File.file?(bundle_binstub)
|
|
16
|
+
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
|
|
17
|
+
load(bundle_binstub)
|
|
18
|
+
else
|
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
require "rubygems"
|
|
25
|
+
require "bundler/setup"
|
|
26
|
+
|
|
27
|
+
load Gem.bin_path("rubocop", "rubocop")
|
data/dev.yml
ADDED
data/lib/rack/sanitizer.rb
CHANGED
|
@@ -7,9 +7,11 @@ module Rack
|
|
|
7
7
|
class Sanitizer
|
|
8
8
|
# options[:sanitizable_content_types] Array
|
|
9
9
|
# options[:additional_content_types] Array
|
|
10
|
-
def initialize(app, options={})
|
|
10
|
+
def initialize(app, options = {})
|
|
11
11
|
@app = app
|
|
12
|
-
|
|
12
|
+
strategy = options.fetch(:strategy) { :replace }
|
|
13
|
+
@strategy = build_strategy(strategy)
|
|
14
|
+
@exception_strategy = strategy == :exception
|
|
13
15
|
@sanitizable_content_types = options[:sanitizable_content_types]
|
|
14
16
|
@sanitizable_content_types ||= SANITIZABLE_CONTENT_TYPES + (options[:additional_content_types] || [])
|
|
15
17
|
end
|
|
@@ -17,25 +19,28 @@ module Rack
|
|
|
17
19
|
def call(env)
|
|
18
20
|
env = sanitize(env)
|
|
19
21
|
begin
|
|
22
|
+
if @exception_strategy && env['rack.input'].is_a?(SanitizedRackInput)
|
|
23
|
+
env['rack.input'].ensure_sanitized
|
|
24
|
+
end
|
|
20
25
|
@app.call(env)
|
|
21
26
|
rescue SanitizedRackInput::FailedToReadBody
|
|
22
|
-
|
|
27
|
+
[400, { "content-type" => "text/plain" }, ["Bad Request"]]
|
|
23
28
|
end
|
|
24
29
|
end
|
|
25
30
|
|
|
26
31
|
DEFAULT_STRATEGIES = {
|
|
27
32
|
replace: lambda do |input|
|
|
28
|
-
input
|
|
29
|
-
force_encoding(Encoding::ASCII_8BIT)
|
|
30
|
-
encode!(Encoding::UTF_8,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
input
|
|
34
|
+
.force_encoding(Encoding::ASCII_8BIT)
|
|
35
|
+
.encode!(Encoding::UTF_8,
|
|
36
|
+
invalid: :replace,
|
|
37
|
+
undef: :replace)
|
|
33
38
|
input
|
|
34
39
|
end,
|
|
35
40
|
exception: lambda do |input|
|
|
36
|
-
input
|
|
37
|
-
force_encoding(Encoding::ASCII_8BIT)
|
|
38
|
-
encode!(Encoding::UTF_8)
|
|
41
|
+
input
|
|
42
|
+
.force_encoding(Encoding::ASCII_8BIT)
|
|
43
|
+
.encode!(Encoding::UTF_8)
|
|
39
44
|
input
|
|
40
45
|
end
|
|
41
46
|
}.freeze
|
|
@@ -73,8 +78,6 @@ module Rack
|
|
|
73
78
|
env[key] = sanitize_uri_encoded_string(value)
|
|
74
79
|
end
|
|
75
80
|
elsif key.to_s.start_with?("HTTP_")
|
|
76
|
-
# Just sanitize the headers and leave them in UTF-8. There is
|
|
77
|
-
# no reason to have UTF-8 in headers, but if it's valid, let it be.
|
|
78
81
|
if value.frozen?
|
|
79
82
|
env[key] = sanitize_string(value.dup).freeze
|
|
80
83
|
else
|
|
@@ -86,9 +89,7 @@ module Rack
|
|
|
86
89
|
|
|
87
90
|
private
|
|
88
91
|
|
|
89
|
-
def build_strategy(
|
|
90
|
-
strategy = options.fetch(:strategy) { :replace }
|
|
91
|
-
|
|
92
|
+
def build_strategy(strategy)
|
|
92
93
|
return strategy unless DEFAULT_STRATEGIES.key?(strategy)
|
|
93
94
|
|
|
94
95
|
DEFAULT_STRATEGIES[strategy]
|
|
@@ -128,7 +129,7 @@ module Rack
|
|
|
128
129
|
return unless env['HTTP_COOKIE']
|
|
129
130
|
|
|
130
131
|
env['HTTP_COOKIE'] = env['HTTP_COOKIE']
|
|
131
|
-
.split(
|
|
132
|
+
.split(/; */n)
|
|
132
133
|
.map { |cookie| sanitize_uri_encoded_string(cookie) }
|
|
133
134
|
.join('; ')
|
|
134
135
|
end
|
|
@@ -157,8 +158,8 @@ module Rack
|
|
|
157
158
|
|
|
158
159
|
def decode_string(input)
|
|
159
160
|
unescape_unreserved(
|
|
160
|
-
sanitize_string(input)
|
|
161
|
-
force_encoding(Encoding::ASCII_8BIT))
|
|
161
|
+
sanitize_string(input)
|
|
162
|
+
.force_encoding(Encoding::ASCII_8BIT))
|
|
162
163
|
end
|
|
163
164
|
|
|
164
165
|
# RFC3986, 2.2 states that the characters from 'reserved' group must be
|
|
@@ -196,16 +197,18 @@ module Rack
|
|
|
196
197
|
|
|
197
198
|
def sanitize_string(input)
|
|
198
199
|
if input.is_a? String
|
|
199
|
-
|
|
200
|
+
# Handle chilled strings
|
|
201
|
+
# Rack values aren't supposed to be frozen, but it's common in test suites.
|
|
202
|
+
input = +input
|
|
200
203
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
else
|
|
207
|
-
input
|
|
204
|
+
input.force_encoding(Encoding::UTF_8)
|
|
205
|
+
|
|
206
|
+
input = @strategy.call(input) unless input.valid_encoding?
|
|
207
|
+
# Rack requires non-ASCII strings to use ASCII-8BIT encoding.
|
|
208
|
+
input = input.b if input.is_a?(String) && !input.ascii_only?
|
|
208
209
|
end
|
|
210
|
+
|
|
211
|
+
input
|
|
209
212
|
end
|
|
210
213
|
end
|
|
211
214
|
|
|
@@ -236,6 +239,10 @@ module Rack
|
|
|
236
239
|
sanitized_io.each(&block)
|
|
237
240
|
end
|
|
238
241
|
|
|
242
|
+
def ensure_sanitized
|
|
243
|
+
sanitized_io
|
|
244
|
+
end
|
|
245
|
+
|
|
239
246
|
def rewind
|
|
240
247
|
sanitized_io.rewind
|
|
241
248
|
end
|
data/rack-sanitizer.gemspec
CHANGED
|
@@ -1,27 +1,22 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
Gem::Specification.new do |gem|
|
|
4
4
|
gem.name = "rack-sanitizer"
|
|
5
|
-
gem.version = '2.0
|
|
6
|
-
gem.authors = ["Jean Boussier", "whitequark"]
|
|
5
|
+
gem.version = '2.1.0'
|
|
6
|
+
gem.authors = ["Shopify - Ruby and Rails Infratructure", "Jean Boussier", "whitequark"]
|
|
7
7
|
gem.license = "MIT"
|
|
8
|
-
gem.email = ["
|
|
9
|
-
gem.description = %{Rack::Sanitizer is a Rack middleware which cleans up }
|
|
8
|
+
gem.email = ["ruby-and-rails-infrastructure@shopify.com"]
|
|
9
|
+
gem.description = %{Rack::Sanitizer is a Rack middleware which cleans up } +
|
|
10
10
|
%{invalid UTF8 characters in request URI and headers.}
|
|
11
11
|
gem.summary = "It is a mordernized and optimized fork of rack-utf8_sanitizer"
|
|
12
12
|
gem.homepage = "http://github.com/Shopify/rack-sanitizer"
|
|
13
13
|
|
|
14
14
|
gem.files = `git ls-files`.split($/)
|
|
15
|
-
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
16
15
|
gem.require_paths = ["lib"]
|
|
17
16
|
|
|
18
17
|
gem.metadata["allowed_push_host"] = "https://rubygems.org/"
|
|
19
18
|
|
|
20
19
|
gem.required_ruby_version = '>= 2.5'
|
|
21
20
|
|
|
22
|
-
gem.add_dependency
|
|
23
|
-
|
|
24
|
-
gem.add_development_dependency "bacon"
|
|
25
|
-
gem.add_development_dependency "bacon-colored_output"
|
|
26
|
-
gem.add_development_dependency "rake"
|
|
21
|
+
gem.add_dependency "rack", '>= 1.0', '< 4.0'
|
|
27
22
|
end
|