diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10829da..7a28843 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - ruby: ["2.5", "2.6", "2.7", "3.0", "3.1", "3.2", ruby-head, jruby-9.2, jruby-9.3, jruby-head] + ruby: ["2.5", "2.6", "2.7", "3.0", "3.1", "3.2", "3.3", ruby-head, jruby-9.2, jruby-9.3, jruby-head] steps: - uses: actions/checkout@v4 diff --git a/Gemfile b/Gemfile index 485407f..76e07c2 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + source 'https://rubygems.org' # Specify your gem's dependencies in rack-utf8_sanitizer.gemspec diff --git a/Rakefile b/Rakefile index 78696fa..775f179 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "bundler/gem_tasks" task :default => :spec diff --git a/lib/rack/utf8_sanitizer.rb b/lib/rack/utf8_sanitizer.rb index e36185d..7486b13 100644 --- a/lib/rack/utf8_sanitizer.rb +++ b/lib/rack/utf8_sanitizer.rb @@ -1,4 +1,5 @@ # encoding: ascii-8bit +# frozen_string_literal: true require 'uri' require 'stringio' @@ -64,20 +65,20 @@ def call(env) ORIGINAL_FULLPATH ORIGINAL_SCRIPT_NAME SERVER_NAME - ).map(&:freeze).freeze + ).freeze SANITIZABLE_CONTENT_TYPES = %w( text/plain application/x-www-form-urlencoded application/json text/javascript - ).map(&:freeze).freeze + ).freeze URI_ENCODED_CONTENT_TYPES = %w( application/x-www-form-urlencoded - ).map(&:freeze).freeze + ).freeze - HTTP_ = 'HTTP_'.freeze + HTTP_ = 'HTTP_' def sanitize(env) sanitize_rack_input(env) @@ -280,7 +281,7 @@ def transfer_frozen(from, to) end end - UTF8_BOM = "\xef\xbb\xbf".force_encoding(Encoding::BINARY).freeze + UTF8_BOM = "\xef\xbb\xbf".dup.force_encoding(Encoding::BINARY).freeze UTF8_BOM_SIZE = UTF8_BOM.bytesize def strip_byte_order_mark(input) diff --git a/rack-utf8_sanitizer.gemspec b/rack-utf8_sanitizer.gemspec index 19df8f2..0bcef67 100644 --- a/rack-utf8_sanitizer.gemspec +++ b/rack-utf8_sanitizer.gemspec @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: true Gem::Specification.new do |gem| gem.name = "rack-utf8_sanitizer" @@ -6,8 +7,8 @@ Gem::Specification.new do |gem| gem.authors = ["whitequark"] gem.license = "MIT" gem.email = ["whitequark@whitequark.org"] - gem.description = %{Rack::UTF8Sanitizer is a Rack middleware which cleans up } << - %{invalid UTF8 characters in request URI and headers.} + gem.description = "Rack::UTF8Sanitizer is a Rack middleware which cleans up " \ + "invalid UTF8 characters in request URI and headers." gem.summary = gem.description gem.homepage = "http://github.com/whitequark/rack-utf8_sanitizer" diff --git a/test/test_utf8_sanitizer.rb b/test/test_utf8_sanitizer.rb index e3f4f87..7f17470 100644 --- a/test/test_utf8_sanitizer.rb +++ b/test/test_utf8_sanitizer.rb @@ -1,4 +1,5 @@ # encoding:ascii-8bit +# frozen_string_literal: true require 'bacon/colored_output' require 'cgi' @@ -31,7 +32,7 @@ describe "with invalid host input" do it "sanitizes host entity (SERVER_NAME)" do - host = "host\xD0".force_encoding('UTF-8') + host = "host\xD0".dup.force_encoding('UTF-8') env = @app.({ "SERVER_NAME" => host }) result = env["SERVER_NAME"] @@ -42,8 +43,8 @@ describe "with invalid UTF-8 input" do before do - @plain_input = "foo\xe0".force_encoding('UTF-8') - @uri_input = "http://bar/foo%E0".force_encoding('UTF-8') + @plain_input = "foo\xe0".dup.force_encoding('UTF-8') + @uri_input = "http://bar/foo%E0".dup.force_encoding('UTF-8') end behaves_like :does_sanitize_plain @@ -52,7 +53,7 @@ describe "with invalid, incorrectly percent-encoded UTF-8 URI input" do before do - @uri_input = "http://bar/foo%E0\xe0".force_encoding('UTF-8') + @uri_input = "http://bar/foo%E0\xe0".dup.force_encoding('UTF-8') end behaves_like :does_sanitize_uri @@ -100,8 +101,8 @@ describe "with valid UTF-8 input" do before do - @plain_input = "foo bar лол".force_encoding('UTF-8') - @uri_input = "http://bar/foo+bar+%D0%BB%D0%BE%D0%BB".force_encoding('UTF-8') + @plain_input = "foo bar лол".dup.force_encoding('UTF-8') + @uri_input = "http://bar/foo+bar+%D0%BB%D0%BE%D0%BB".dup.force_encoding('UTF-8') end behaves_like :identity_plain @@ -109,7 +110,7 @@ describe "with URI characters from reserved range" do before do - @uri_input = "http://bar/foo+%2F%3A+bar+%D0%BB%D0%BE%D0%BB".force_encoding('UTF-8') + @uri_input = "http://bar/foo+%2F%3A+bar+%D0%BB%D0%BE%D0%BB".dup.force_encoding('UTF-8') end behaves_like :identity_uri @@ -118,7 +119,7 @@ describe "with valid, not percent-encoded UTF-8 URI input" do before do - @uri_input = "http://bar/foo+bar+лол".force_encoding('UTF-8') + @uri_input = "http://bar/foo+bar+лол".dup.force_encoding('UTF-8') @encoded = "http://bar/foo+bar+#{CGI.escape("лол")}" end @@ -152,8 +153,8 @@ describe "with frozen strings" do before do - @plain_input = "bar baz".freeze - @uri_input = "http://bar/bar+baz".freeze + @plain_input = "bar baz" + @uri_input = "http://bar/bar+baz" end it "preserves the frozen? status of input" do @@ -165,9 +166,24 @@ end end + describe "with mutable strings" do + before do + @plain_input = "bar baz".dup + @uri_input = "http://bar/bar+baz".dup + end + + it "preserves the frozen? status of input" do + env = @app.({ "HTTP_USER_AGENT" => @plain_input, + "REQUEST_PATH" => @uri_input }) + + env["HTTP_USER_AGENT"].should.not.be.frozen + env["REQUEST_PATH"].should.not.be.frozen + end + end + describe "with symbols in the env" do before do - @uri_input = "http://bar/foo%E0\xe0".force_encoding('UTF-8') + @uri_input = "http://bar/foo%E0\xe0".dup.force_encoding('UTF-8') end it "sanitizes REQUEST_PATH with invalid UTF-8 URI input" do @@ -183,7 +199,7 @@ describe "with form data" do def request_env - @plain_input = "foo bar лол".force_encoding('UTF-8') + @plain_input = "foo bar лол".dup.force_encoding('UTF-8') { "REQUEST_METHOD" => "POST", "CONTENT_TYPE" => "application/x-www-form-urlencoded;foo=bar", @@ -193,7 +209,7 @@ def request_env end def sanitize_form_data(request_env = request_env()) - @uri_input = "http://bar/foo+%2F%3A+bar+%D0%BB%D0%BE%D0%BB".force_encoding('UTF-8') + @uri_input = "http://bar/foo+%2F%3A+bar+%D0%BB%D0%BE%D0%BB".dup.force_encoding('UTF-8') @response_env = @app.(request_env) sanitized_input = @response_env['rack.input'].read @@ -468,7 +484,7 @@ def request_env describe "with custom content-type" do def request_env - @plain_input = "foo bar лол".force_encoding('UTF-8') + @plain_input = "foo bar лол".dup.force_encoding('UTF-8') { "REQUEST_METHOD" => "POST", "CONTENT_TYPE" => "application/vnd.api+json", @@ -478,7 +494,7 @@ def request_env end def sanitize_data(request_env = request_env()) - @uri_input = "http://bar/foo+%2F%3A+bar+%D0%BB%D0%BE%D0%BB".force_encoding('UTF-8') + @uri_input = "http://bar/foo+%2F%3A+bar+%D0%BB%D0%BE%D0%BB".dup.force_encoding('UTF-8') @response_env = @app.(request_env) sanitized_input = @response_env['rack.input'].read @@ -552,7 +568,7 @@ def sanitize_data(request_env = request_env()) describe "with only and/or except options" do before do - @plain_input = "foo\xe0".force_encoding('UTF-8') + @plain_input = "foo\xe0".dup.force_encoding('UTF-8') end def request_env @@ -609,7 +625,7 @@ def sanitize_data(request_env = request_env()) describe "with custom strategy" do def request_env - @plain_input = "foo bar лол".force_encoding('UTF-8') + @plain_input = "foo bar лол".dup.force_encoding('UTF-8') { "REQUEST_METHOD" => "POST", "CONTENT_TYPE" => "application/json", @@ -619,7 +635,7 @@ def request_env end def sanitize_data(request_env = request_env()) - @uri_input = "http://bar/foo+%2F%3A+bar+%D0%BB%D0%BE%D0%BB".force_encoding('UTF-8') + @uri_input = "http://bar/foo+%2F%3A+bar+%D0%BB%D0%BE%D0%BB".dup.force_encoding('UTF-8') @response_env = @app.(request_env) sanitized_input = @response_env['rack.input'].read @@ -653,7 +669,7 @@ def sanitize_data(request_env = request_env()) it "accepts a proc as a strategy" do truncate = -> (input, sanitize_null_bytes:) do sanitize_null_bytes.should == false - 'replace'.force_encoding(Encoding::UTF_8) + "replace".dup.force_encoding(Encoding::UTF_8) end @app = Rack::UTF8Sanitizer.new(-> env { env }, strategy: truncate) @@ -672,7 +688,7 @@ def sanitize_data(request_env = request_env()) it "accepts a proc as a strategy and passes along sanitize_null_bytes" do truncate = -> (input, sanitize_null_bytes:) do sanitize_null_bytes.should == true - 'replace'.force_encoding(Encoding::UTF_8) + "replace".dup.force_encoding(Encoding::UTF_8) end @app = Rack::UTF8Sanitizer.new(-> env { env }, sanitize_null_bytes: true, strategy: truncate)