Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the frozen_string_literal header #90

Merged
merged 1 commit into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = ["[email protected]"]
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