Skip to content

Commit

Permalink
Add the frozen_string_literal header
Browse files Browse the repository at this point in the history
I think after Ruby 3.4 is released, more people will start expecting
libraries to be functional with eg the `--enable-frozen-string-literal`
option enabled.

This commit adds the frozen_string_literal header to all files, fixes
failing tests, and removes now unnecessary freezing of strings.
  • Loading branch information
bquorning committed Oct 28, 2024
1 parent f2bcf86 commit 7e5e57e
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

source 'https://rubygems.org'

# Specify your gem's dependencies in rack-utf8_sanitizer.gemspec
Expand Down
2 changes: 2 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require "bundler/gem_tasks"

task :default => :spec
Expand Down
11 changes: 6 additions & 5 deletions lib/rack/utf8_sanitizer.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: ascii-8bit
# frozen_string_literal: true

require 'uri'
require 'stringio'
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions rack-utf8_sanitizer.gemspec
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# -*- encoding: utf-8 -*-
# frozen_string_literal: true

Gem::Specification.new do |gem|
gem.name = "rack-utf8_sanitizer"
gem.version = '1.9.1'
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"

Expand Down
56 changes: 36 additions & 20 deletions test/test_utf8_sanitizer.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding:ascii-8bit
# frozen_string_literal: true

require 'bacon/colored_output'
require 'cgi'
Expand Down Expand Up @@ -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"]

Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -100,16 +101,16 @@

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
behaves_like :identity_uri

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
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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",
Expand All @@ -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

Expand Down Expand Up @@ -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",
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand All @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down

0 comments on commit 7e5e57e

Please sign in to comment.