summary refs log tree commit
diff options
context:
space:
mode:
authorAnatolii Didukh <railsme@users.noreply.github.com>2019-10-16 09:32:13 +0300
committerGitHub <noreply@github.com>2019-10-16 09:32:13 +0300
commit13f444a78998e8fd7b1de37495dba6beba93efd7 (patch)
treefd4f1ae4f4296ce9553dc610de7520e77e25191a
parentba67b778d54b7213c0e7f1a9073885fe5117aeb2 (diff)
parentfb2668fec7b9ba2bfdabf72bfc4c8c53518c9abb (diff)
downloadrack-13f444a78998e8fd7b1de37495dba6beba93efd7.tar.gz
Merge branch 'master' into optimize-static
-rw-r--r--.circleci/config.yml96
-rw-r--r--.rubocop.yml48
-rw-r--r--.travis.yml37
-rw-r--r--CHANGELOG.md (renamed from HISTORY.md)294
-rw-r--r--Gemfile12
-rw-r--r--MIT-LICENSE (renamed from COPYING)6
-rw-r--r--NEWS14
-rw-r--r--README.rdoc125
-rw-r--r--Rakefile43
-rw-r--r--SECURITY_POLICY.md17
-rw-r--r--SPEC20
-rwxr-xr-xbin/rackup1
-rw-r--r--circle.yml6
-rw-r--r--example/lobster.ru2
-rw-r--r--example/protectedlobster.rb4
-rw-r--r--example/protectedlobster.ru2
-rw-r--r--lib/rack.rb122
-rw-r--r--lib/rack/auth/abstract/handler.rb4
-rw-r--r--lib/rack/auth/abstract/request.rb8
-rw-r--r--lib/rack/auth/basic.rb4
-rw-r--r--lib/rack/auth/digest/md5.rb16
-rw-r--r--lib/rack/auth/digest/nonce.rb2
-rw-r--r--lib/rack/auth/digest/params.rb6
-rw-r--r--lib/rack/auth/digest/request.rb2
-rw-r--r--lib/rack/body_proxy.rb4
-rw-r--r--lib/rack/builder.rb53
-rw-r--r--lib/rack/cascade.rb6
-rw-r--r--lib/rack/chunked.rb33
-rw-r--r--lib/rack/common_logger.rb15
-rw-r--r--lib/rack/conditional_get.rb4
-rw-r--r--lib/rack/config.rb2
-rw-r--r--lib/rack/content_length.rb4
-rw-r--r--lib/rack/content_type.rb4
-rw-r--r--lib/rack/core_ext/regexp.rb16
-rw-r--r--lib/rack/deflater.rb82
-rw-r--r--lib/rack/directory.rb26
-rw-r--r--lib/rack/etag.rb6
-rw-r--r--lib/rack/events.rb8
-rw-r--r--lib/rack/file.rb16
-rw-r--r--lib/rack/handler.rb4
-rw-r--r--lib/rack/handler/cgi.rb4
-rw-r--r--lib/rack/handler/fastcgi.rb6
-rw-r--r--lib/rack/handler/lsws.rb4
-rw-r--r--lib/rack/handler/scgi.rb15
-rw-r--r--lib/rack/handler/thin.rb4
-rw-r--r--lib/rack/handler/webrick.rb14
-rw-r--r--lib/rack/head.rb2
-rw-r--r--lib/rack/lint.rb19
-rw-r--r--lib/rack/lobster.rb12
-rw-r--r--lib/rack/lock.rb17
-rw-r--r--lib/rack/logger.rb2
-rw-r--r--lib/rack/media_type.rb13
-rw-r--r--lib/rack/method_override.rb15
-rw-r--r--lib/rack/mime.rb10
-rw-r--r--lib/rack/mock.rb97
-rw-r--r--lib/rack/multipart.rb2
-rw-r--r--lib/rack/multipart/generator.rb13
-rw-r--r--lib/rack/multipart/parser.rb107
-rw-r--r--lib/rack/multipart/uploaded_file.rb2
-rw-r--r--lib/rack/null_logger.rb2
-rw-r--r--lib/rack/query_parser.rb39
-rw-r--r--lib/rack/recursive.rb12
-rw-r--r--lib/rack/reloader.rb14
-rw-r--r--lib/rack/request.rb114
-rw-r--r--lib/rack/response.rb25
-rw-r--r--lib/rack/rewindable_input.rb4
-rw-r--r--lib/rack/runtime.rb6
-rw-r--r--lib/rack/sendfile.rb19
-rw-r--r--lib/rack/server.rb107
-rw-r--r--lib/rack/session/abstract/id.rb66
-rw-r--r--lib/rack/session/cookie.rb14
-rw-r--r--lib/rack/session/memcache.rb15
-rw-r--r--lib/rack/session/pool.rb6
-rw-r--r--lib/rack/show_exceptions.rb26
-rw-r--r--lib/rack/show_status.rb6
-rw-r--r--lib/rack/simple_body_proxy.rb13
-rw-r--r--lib/rack/static.rb19
-rw-r--r--lib/rack/tempfile_reaper.rb2
-rw-r--r--lib/rack/urlmap.rb13
-rw-r--r--lib/rack/utils.rb122
-rw-r--r--rack.gemspec35
-rw-r--r--test/builder/an_underscore_app.rb4
-rw-r--r--test/builder/anything.rb5
-rw-r--r--test/builder/bom.ru1
-rw-r--r--test/builder/comment.ru4
-rw-r--r--test/builder/end.ru4
-rw-r--r--test/builder/line.ru4
-rw-r--r--test/builder/options.ru4
-rwxr-xr-xtest/cgi/rackup_stub.rb2
-rwxr-xr-xtest/cgi/sample_rackup.ru2
-rwxr-xr-xtest/cgi/test2
-rwxr-xr-xtest/cgi/test.fcgi2
-rw-r--r--test/cgi/test.gzbin165 -> 187 bytes
-rwxr-xr-xtest/cgi/test.ru2
-rw-r--r--test/gemloader.rb2
-rw-r--r--test/helper.rb4
-rw-r--r--test/multipart/filename_with_null_byte7
-rw-r--r--test/multipart/filename_with_plus6
-rw-r--r--test/rackup/config.ru2
-rw-r--r--test/registering_handler/rack/handler/registering_myself.rb2
-rw-r--r--test/spec_auth_basic.rb13
-rw-r--r--test/spec_auth_digest.rb14
-rw-r--r--test/spec_body_proxy.rb6
-rw-r--r--test/spec_builder.rb66
-rw-r--r--test/spec_cascade.rb6
-rw-r--r--test/spec_cgi.rb8
-rw-r--r--test/spec_chunked.rb44
-rw-r--r--test/spec_common_logger.rb10
-rw-r--r--test/spec_conditional_get.rb20
-rw-r--r--test/spec_config.rb6
-rw-r--r--test/spec_content_length.rb24
-rw-r--r--test/spec_content_type.rb30
-rw-r--r--test/spec_deflater.rb114
-rw-r--r--test/spec_directory.rb14
-rw-r--r--test/spec_etag.rb32
-rw-r--r--test/spec_events.rb2
-rw-r--r--test/spec_fastcgi.rb8
-rw-r--r--test/spec_file.rb35
-rw-r--r--test/spec_handler.rb4
-rw-r--r--test/spec_head.rb6
-rw-r--r--test/spec_lint.rb82
-rw-r--r--test/spec_lobster.rb4
-rw-r--r--test/spec_lock.rb40
-rw-r--r--test/spec_logger.rb6
-rw-r--r--test/spec_media_type.rb8
-rw-r--r--test/spec_method_override.rb55
-rw-r--r--test/spec_mime.rb6
-rw-r--r--test/spec_mock.rb124
-rw-r--r--test/spec_multipart.rb50
-rw-r--r--test/spec_null_logger.rb8
-rw-r--r--test/spec_recursive.rb4
-rw-r--r--test/spec_request.rb118
-rw-r--r--test/spec_response.rb114
-rw-r--r--test/spec_rewindable_input.rb14
-rw-r--r--test/spec_runtime.rb12
-rw-r--r--test/spec_sendfile.rb70
-rw-r--r--test/spec_server.rb62
-rw-r--r--test/spec_session_abstract_id.rb6
-rw-r--r--test/spec_session_abstract_session_hash.rb23
-rw-r--r--test/spec_session_cookie.rb166
-rw-r--r--test/spec_session_memcache.rb40
-rw-r--r--test/spec_session_pool.rb20
-rw-r--r--test/spec_show_exceptions.rb63
-rw-r--r--test/spec_show_status.rb28
-rw-r--r--test/spec_static.rb70
-rw-r--r--test/spec_tempfile_reaper.rb4
-rw-r--r--test/spec_thin.rb12
-rw-r--r--test/spec_urlmap.rb14
-rw-r--r--test/spec_utils.rb183
-rw-r--r--test/spec_version.rb5
-rw-r--r--test/spec_webrick.rb62
-rw-r--r--test/testrequest.rb12
-rw-r--r--test/unregistered_handler/rack/handler/unregistered.rb2
-rw-r--r--test/unregistered_handler/rack/handler/unregistered_long_one.rb2
154 files changed, 2663 insertions, 1558 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 00000000..b778316a
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,96 @@
+workflows:
+  version: 2
+
+  test:
+    jobs:
+      - test-jruby
+      - test-ruby-2.2
+      - test-ruby-2.3
+      - test-ruby-2.4
+      - test-ruby-2.5
+      - test-ruby-2.6
+
+version: 2
+
+default-steps: &default-steps
+  - checkout
+  - run: sudo apt-get install lighttpd libfcgi-dev libmemcached-dev
+
+  # Restore bundle cache
+  - type: cache-restore
+    key: rack-{{ checksum "rack.gemspec" }}-{{ checksum "Gemfile" }}
+
+  # Bundle install dependencies
+  - run: bundle install --path vendor/bundle
+
+  # Store bundle cache
+  - type: cache-save
+    key: rack-{{ checksum "rack.gemspec" }}-{{ checksum "Gemfile" }}
+    paths:
+      - vendor/bundle
+
+  - run: bundle exec rubocop
+
+  - run: bundle exec rake ci
+
+jobs:
+  test-ruby-2.2:
+    docker:
+      - image: circleci/ruby:2.2
+        # Spawn a process owned by root
+        # This works around an issue explained here:
+        # https://github.com/circleci/circleci-images/pull/132
+        command: sudo /bin/sh
+      - image: memcached:1.4
+    steps: *default-steps
+
+  test-ruby-2.3:
+    docker:
+      - image: circleci/ruby:2.3
+        # Spawn a process owned by root
+        # This works around an issue explained here:
+        # https://github.com/circleci/circleci-images/pull/132
+        command: sudo /bin/sh
+      - image: memcached:1.4
+    steps: *default-steps
+
+  test-ruby-2.4:
+    docker:
+      - image: circleci/ruby:2.4
+        # Spawn a process owned by root
+        # This works around an issue explained here:
+        # https://github.com/circleci/circleci-images/pull/132
+        command: sudo /bin/sh
+      - image: memcached:1.4
+    steps: *default-steps
+
+  test-ruby-2.5:
+    docker:
+      - image: circleci/ruby:2.5
+        # Spawn a process owned by root
+        # This works around an issue explained here:
+        # https://github.com/circleci/circleci-images/pull/132
+        command: sudo /bin/sh
+      - image: memcached:1.4
+    steps: *default-steps
+
+  test-ruby-2.6:
+    docker:
+      - image: circleci/ruby:2.6
+        # Spawn a process owned by root
+        # This works around an issue explained here:
+        # https://github.com/circleci/circleci-images/pull/132
+        command: sudo /bin/sh
+      - image: memcached:1.4
+    steps: *default-steps
+
+  test-jruby:
+    docker:
+      - image: circleci/jruby
+        # Spawn a process owned by root
+        # This works around an issue explained here:
+        # https://github.com/circleci/circleci-images/pull/132
+        command: sudo /bin/sh
+      - image: memcached:1.4
+    steps: *default-steps
+
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 00000000..22ed9920
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,48 @@
+AllCops:
+  TargetRubyVersion: 2.2
+  DisabledByDefault: true
+  Exclude:
+    - '**/vendor/**/*'
+
+Style/FrozenStringLiteralComment:
+  Enabled: true
+  EnforcedStyle: always
+  Exclude:
+    - 'test/builder/bom.ru'
+
+# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
+Style/HashSyntax:
+  Enabled: true
+
+Layout/EmptyLineAfterMagicComment:
+  Enabled: true
+
+Layout/LeadingCommentSpace:
+  Enabled: true
+  Exclude:
+    - 'test/builder/options.ru'
+
+Layout/SpaceAfterColon:
+  Enabled: true
+
+Layout/SpaceAfterComma:
+  Enabled: true
+
+Layout/SpaceAroundEqualsInParameterDefault:
+  Enabled: true
+
+Layout/SpaceAroundKeyword:
+  Enabled: true
+
+Layout/SpaceAroundOperators:
+  Enabled: true
+
+Layout/SpaceBeforeComma:
+  Enabled: true
+
+Layout/SpaceBeforeFirstArg:
+  Enabled: true
+
+# Use `{ a: 1 }` not `{a:1}`.
+Layout/SpaceInsideHashLiteralBraces:
+  Enabled: true
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index cf999939..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-language: ruby
-sudo: false
-cache:
-  - bundler
-  - apt
-
-services:
-  - memcached
-
-addons:
-  apt:
-    packages:
-      - lighttpd
-      - libfcgi-dev
-
-before_install:
-  - gem env version | grep '^\(2\|1.\(8\|9\|[0-9][0-9]\)\)' || gem update --system
-  - gem list -i bundler || gem install bundler
-
-script: bundle exec rake ci
-
-rvm:
-  - 2.2.5
-  - 2.3.1
-  - ruby-head
-  - rbx-2
-  - jruby-9.0.4.0
-  - jruby-head
-
-notifications:
-  email: false
-  irc: "irc.freenode.org#rack"
-
-matrix:
-  allow_failures:
-    - rvm: rbx-2
-    - rvm: jruby-head
diff --git a/HISTORY.md b/CHANGELOG.md
index 406d1758..1286d14a 100644
--- a/HISTORY.md
+++ b/CHANGELOG.md
@@ -1,162 +1,96 @@
-Sun Dec 4 18:48:03 2015  Jeremy Daer <jeremydaer@gmail.com>
+# Changelog
+All notable changes to this project will be documented in this file. For info on how to format all future additions to this file please reference [Keep A Changelog](https://keepachangelog.com/en/1.0.0/)
 
-        * First-party "SameSite" cookies. Browsers omit SameSite cookies
-        from third-party requests, closing the door on many CSRF attacks.
+## [Unreleased]
+### Added
+- CHANGELOG.md using keep a changelog formatting by @twitnithegirl
 
-        Pass `same_site: true` (or `:strict`) to enable:
-            response.set_cookie 'foo', value: 'bar', same_site: true
-        or `same_site: :lax` to use Lax enforcement:
-            response.set_cookie 'foo', value: 'bar', same_site: :lax
+### Changed
+- `Rack::Utils.status_code` now raises an error when the status symbol is invalid instead of `500`.
+- `Rack::Request::SCHEME_WHITELIST` has been renamed to `Rack::Request::ALLOWED_SCHEMES`
+- `Rack::Multipart::Parser.get_filename` now accepts file that contains `+` in its name, avoiding the replacement of `+` to space character since filenames with `+` are valid.
 
-        Based on version 7 of the Same-site Cookies internet draft:
-        https://tools.ietf.org/html/draft-west-first-party-cookies-07
-
-        Thanks to Ben Toews (@mastahyeti) and Bob Long (@bobjflong) for
-        updating to drafts 5 and 7.
-
-Tue Nov  3 16:17:26 2015  Aaron Patterson <tenderlove@ruby-lang.org>
-
-        * Add `Rack::Events` middleware for adding event based middleware:
-        middleware that does not care about the response body, but only cares
-        about doing work at particular points in the request / response
-        lifecycle.
-
-Thu Oct  8 14:58:46 2015  Aaron Patterson <tenderlove@ruby-lang.org>
-
-        * Add `Rack::Request#authority` to calculate the authority under which
-        the response is being made (this will be handy for h2 pushes).
-
-Tue Oct  6 13:19:04 2015  Aaron Patterson <tenderlove@ruby-lang.org>
-
-        * Add `Rack::Response::Helpers#cache_control` and `cache_control=`.
-        Use this for setting cache control headers on your response objects.
-
-Tue Oct  6 13:12:21 2015  Aaron Patterson <tenderlove@ruby-lang.org>
-
-        * Add `Rack::Response::Helpers#etag` and `etag=`.  Use this for
-        setting etag values on the response.
-
-Sun Oct 3 18:25:03 2015  Jeremy Daer <jeremydaer@gmail.com>
-
-        * Introduce `Rack::Response::Helpers#add_header` to add a value to a
-        multi-valued response header. Implemented in terms of other
-        `Response#*_header` methods, so it's available to any response-like
-        class that includes the `Helpers` module.
-
-        * Add `Rack::Request#add_header` to match.
-
-Fri Sep  4 18:34:53 2015  Aaron Patterson <tenderlove@ruby-lang.org>
-
-        * `Rack::Session::Abstract::ID` IS DEPRECATED.  Please switch to
-        `Rack::Session::Abstract::Persisted`.
-        `Rack::Session::Abstract::Persisted` uses a request object rather than
-        the `env` hash.
-
-Fri Sep  4 17:32:12 2015  Aaron Patterson <tenderlove@ruby-lang.org>
-
-        * Pull `ENV` access inside the request object in to a module.  This
-        will help with legacy Request objects that are ENV based but don't
-        want to inherit from Rack::Request
-
-Fri Sep  4 16:09:11 2015  Aaron Patterson <tenderlove@ruby-lang.org>
+### Removed
+- HISTORY.md by @twitnithegirl
+- NEWS.md by @twitnithegirl
 
-        * Move most methods on the `Rack::Request` to a module
-        `Rack::Request::Helpers` and use public API to get values from the
-        request object.  This enables users to mix `Rack::Request::Helpers` in
-        to their own objects so they can implement
-        `(get|set|fetch|each)_header` as they see fit (for example a proxy
-        object).
 
-Fri Sep  4 14:15:32 2015  Aaron Patterson <tenderlove@ruby-lang.org>
+#
+#
+# History/News Archive
+Items below this line are from the previously maintained HISTORY.md and NEWS.md files.
+#
 
-        * Files and directories with + in the name are served correctly.
-        Rather than unescaping paths like a form, we unescape with a URI
-        parser using `Rack::Utils.unescape_path`. Fixes #265
+## [2.0.0]
+- Rack::Session::Abstract::ID is deprecated. Please change to use Rack::Session::Abstract::Persisted
 
-Thu Aug 27 15:43:48 2015  Aaron Patterson <tenderlove@ruby-lang.org>
-
-        * Tempfiles are automatically closed in the case that there were too
+## [2.0.0.alpha] 2015-12-04
+- First-party "SameSite" cookies. Browsers omit SameSite cookies from third-party requests, closing the door on many CSRF attacks.
+- Pass `same_site: true` (or `:strict`) to enable: response.set_cookie 'foo', value: 'bar', same_site: true or `same_site: :lax` to use Lax enforcement: response.set_cookie 'foo', value: 'bar', same_site: :lax
+- Based on version 7 of the Same-site Cookies internet draft:
+        https://tools.ietf.org/html/draft-west-first-party-cookies-07
+- Thanks to Ben Toews (@mastahyeti) and Bob Long (@bobjflong) for updating to drafts 5 and 7.
+- Add `Rack::Events` middleware for adding event based middleware: middleware that does not care about the response body, but only cares about doing work at particular points in the request / response lifecycle.
+- Add `Rack::Request#authority` to calculate the authority under which the response is being made (this will be handy for h2 pushes).
+- Add `Rack::Response::Helpers#cache_control` and `cache_control=`. Use this for setting cache control headers on your response objects.
+- Add `Rack::Response::Helpers#etag` and `etag=`.  Use this for setting etag values on the response.
+- Introduce `Rack::Response::Helpers#add_header` to add a value to a multi-valued response header. Implemented in terms of other `Response#*_header` methods, so it's available to any response-like class that includes the `Helpers` module.
+- Add `Rack::Request#add_header` to match.
+- `Rack::Session::Abstract::ID` IS DEPRECATED.  Please switch to `Rack::Session::Abstract::Persisted`. `Rack::Session::Abstract::Persisted` uses a request object rather than the `env` hash.
+- Pull `ENV` access inside the request object in to a module.  This will help with legacy Request objects that are ENV based but don't want to inherit from Rack::Request
+- Move most methods on the `Rack::Request` to a module `Rack::Request::Helpers` and use public API to get values from the request object.  This enables users to mix `Rack::Request::Helpers` in to their own objects so they can implement `(get|set|fetch|each)_header` as they see fit (for example a proxy object).
+- Files and directories with + in the name are served correctly. Rather than unescaping paths like a form, we unescape with a URI parser using `Rack::Utils.unescape_path`. Fixes #265
+- Tempfiles are automatically closed in the case that there were too
         many posted.
-
-Thu Aug 27 11:00:03 2015  Aaron Patterson <tenderlove@ruby-lang.org>
-
-        * Added methods for manipulating response headers that don't assume
+- Added methods for manipulating response headers that don't assume
         they're stored as a Hash. Response-like classes may include the
         Rack::Response::Helpers module if they define these methods:
-
-          * Rack::Response#has_header?
-          * Rack::Response#get_header
-          * Rack::Response#set_header
-          * Rack::Response#delete_header
-
-Mon Aug 24 18:05:23 2015  Aaron Patterson <tenderlove@ruby-lang.org>
-
-        * Introduce Util.get_byte_ranges that will parse the value of the
-        HTTP_RANGE string passed to it without depending on the `env` hash.
-        `byte_ranges` is deprecated in favor of this method.
-
-Sat Aug 22 17:49:49 2015  Aaron Patterson <tenderlove@ruby-lang.org>
-
-        * Change Session internals to use Request objects for looking up
-        session information. This allows us to only allocate one request
-        object when dealing with session objects (rather than doing it every
-        time we need to manipulate cookies, etc).
-
-Fri Aug 21 16:30:51 2015  Aaron Patterson <tenderlove@ruby-lang.org>
-
-        * Add `Rack::Request#initialize_copy` so that the env is duped when
-        the request gets duped.
-
-Thu Aug 20 16:20:58 2015  Aaron Patterson <tenderlove@ruby-lang.org>
-
-        * Added methods for manipulating request specific data.  This includes
+    - Rack::Response#has_header?
+        - Rack::Response#get_header
+        - Rack::Response#set_header
+        - Rack::Response#delete_header
+- Introduce Util.get_byte_ranges that will parse the value of the HTTP_RANGE string passed to it without depending on the `env` hash. `byte_ranges` is deprecated in favor of this method.
+- Change Session internals to use Request objects for looking up session information. This allows us to only allocate one request object when dealing with session objects (rather than doing it every time we need to manipulate cookies, etc).
+- Add `Rack::Request#initialize_copy` so that the env is duped when the request gets duped.
+- Added methods for manipulating request specific data.  This includes
         data set as CGI parameters, and just any arbitrary data the user wants
         to associate with a particular request.  New methods:
-
-          * Rack::Request#has_header?
-          * Rack::Request#get_header
-          * Rack::Request#fetch_header
-          * Rack::Request#each_header
-          * Rack::Request#set_header
-          * Rack::Request#delete_header
-
-Thu Jun 18 16:00:05 2015  Aaron Patterson <tenderlove@ruby-lang.org>
-
-        *  lib/rack/utils.rb: add a method for constructing "delete" cookie
+         - Rack::Request#has_header?
+         - Rack::Request#get_header
+         - Rack::Request#fetch_header
+         - Rack::Request#each_header
+         - Rack::Request#set_header
+         - Rack::Request#delete_header
+- lib/rack/utils.rb: add a method for constructing "delete" cookie
         headers.  This allows us to construct cookie headers without depending
         on the side effects of mutating a hash.
-
-Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
-
-        * Prevent extremely deep parameters from being parsed. CVE-2015-3225
-
-### May 6th, 2015, Thirty seventh public release 1.6.1
-  - Fix CVE-2014-9490, denial of service attack in OkJson ([8cd610](https://github.com/rack/rack/commit/8cd61062954f70e0a03e2855704e95ff4bdd4f6e))
-  - Use a monotonic time for Rack::Runtime, if available ([d170b2](https://github.com/rack/rack/commit/d170b2363c949dce60871f9d5a6bfc83da2bedb5))
-  - RACK_MULTIPART_LIMIT changed to RACK_MULTIPART_PART_LIMIT (RACK_MULTIPART_LIMIT is deprecated and will be removed in 1.7.0) ([c096c5](https://github.com/rack/rack/commit/c096c50c00230d8eee13ad5f79ad027d9a3f3ca9))
-  - See the full [git history](https://github.com/rack/rack/compare/1.6.0...1.6.1) and [milestone tag](https://github.com/rack/rack/issues?utf8=%E2%9C%93&q=milestone%3A%22Rack+1.6%22)
-
-### May 6th, 2015, Thirty seventh public release 1.5.3
-  - Fix CVE-2014-9490, denial of service attack in OkJson ([99f725](https://github.com/rack/rack/commit/99f725b583b357376ffbb7b3b042c5daa3106ad6))
-  - Backport bug fixes to 1.5 series ([#585](https://github.com/rack/rack/pull/585), [#711](https://github.com/rack/rack/pull/711), [#756](https://github.com/rack/rack/pull/756))
-  - See the full [git history](https://github.com/rack/rack/compare/1.5.2...1.5.3) and [milestone tag](https://github.com/rack/rack/issues?utf8=%E2%9C%93&q=milestone%3A%22Rack+1.5.3%22)
-
-### December 18th, 2014, Thirty sixth public release 1.6.0
-  - Response#unauthorized? helper ([#580](https://github.com/rack/rack/pull/580))
-  - Deflater now accepts an options hash to control compression on a per-request level ([#457](https://github.com/rack/rack/pull/457))
-  - Builder#warmup method for app preloading ([#617](https://github.com/rack/rack/pull/617))
-  - Request#accept_language method to extract HTTP_ACCEPT_LANGUAGE ([#623](https://github.com/rack/rack/pull/623))
-  - Add quiet mode of rack server, rackup --quiet ([#674](https://github.com/rack/rack/pull/674))
-  - Update HTTP Status Codes to RFC 7231 ([#754](https://github.com/rack/rack/pull/754))
-  - Less strict header name validation according to [RFC 2616](https://tools.ietf.org/html/rfc2616) ([#399](https://github.com/rack/rack/pull/399))
-    - SPEC updated to specify headers conform to RFC7230 specification ([6839fc](https://github.com/rack/rack/commit/6839fc203339f021cb3267fb09cba89410f086e9))
-  - Etag correctly marks etags as weak ([#681](https://github.com/rack/rack/issues/681))
-  - Request#port supports multiple x-http-forwarded-proto values ([#669](https://github.com/rack/rack/pull/669))
-  - Utils#multipart_part_limit configures the maximum number of parts a request can contain ([#684](https://github.com/rack/rack/pull/684))
-  - Default host to localhost when in development mode ([#514](https://github.com/rack/rack/pull/514))
-  - Various bugfixes and performance improvements (See the full [git history](https://github.com/rack/rack/compare/1.5.2...1.6.0) and [milestone tag](https://github.com/rack/rack/issues?utf8=%E2%9C%93&q=milestone%3A%22Rack+1.6%22))
-
-### February 7th, 2013, Thirty fifth public release 1.5.2
+- Prevent extremely deep parameters from being parsed. CVE-2015-3225
+
+## [1.6.1] 2015-05-06
+  - Fix CVE-2014-9490, denial of service attack in OkJson
+  - Use a monotonic time for Rack::Runtime, if available
+  - RACK_MULTIPART_LIMIT changed to RACK_MULTIPART_PART_LIMIT (RACK_MULTIPART_LIMIT is deprecated and will be removed in 1.7.0)
+
+## [1.5.3] 2015-05-06
+  - Fix CVE-2014-9490, denial of service attack in OkJson
+  - Backport bug fixes to 1.5 series
+
+## [1.6.0] 2014-01-18
+  - Response#unauthorized? helper
+  - Deflater now accepts an options hash to control compression on a per-request level
+  - Builder#warmup method for app preloading
+  - Request#accept_language method to extract HTTP_ACCEPT_LANGUAGE
+  - Add quiet mode of rack server, rackup --quiet
+  - Update HTTP Status Codes to RFC 7231
+  - Less strict header name validation according to RFC 2616
+  - SPEC updated to specify headers conform to RFC7230 specification
+  - Etag correctly marks etags as weak
+  - Request#port supports multiple x-http-forwarded-proto values
+  - Utils#multipart_part_limit configures the maximum number of parts a request can contain
+  - Default host to localhost when in development mode
+  - Various bugfixes and performance improvements
+
+## [1.5.2] 2013-02-07
   - Fix CVE-2013-0263, timing attack against Rack::Session::Cookie
   - Fix CVE-2013-0262, symlink path traversal in Rack::File
   - Add various methods to Session for enhanced Rails compatibility
@@ -166,19 +100,19 @@ Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
   - Fix a race condition that could result in overwritten pidfiles
   - Various documentation additions
 
-### February 7th, 2013, Thirty fifth public release 1.4.5
+## [1.4.5] 2013-02-07
   - Fix CVE-2013-0263, timing attack against Rack::Session::Cookie
   - Fix CVE-2013-0262, symlink path traversal in Rack::File
 
-### February 7th, Thirty fifth public release 1.1.6, 1.2.8, 1.3.10
+## [1.1.6, 1.2.8, 1.3.10] 2013-02-07
   - Fix CVE-2013-0263, timing attack against Rack::Session::Cookie
 
-### January 28th, 2013: Thirty fourth public release 1.5.1
+## [1.5.1] 2013-01-28
   - Rack::Lint check_hijack now conforms to other parts of SPEC
   - Added hash-like methods to Abstract::ID::SessionHash for compatibility
   - Various documentation corrections
 
-### January 21st, 2013: Thirty third public release 1.5.0
+## [1.5.0] 2013-01-21
   - Introduced hijack SPEC, for before-response and after-response hijacking
   - SessionHash is no longer a Hash subclass
   - Rack::File cache_control parameter is removed, in place of headers options
@@ -202,17 +136,17 @@ Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
   - Updated HTTP status codes
   - Ruby 1.8.6 likely no longer passes tests, and is no longer fully supported
 
-### January 13th, 2013: Thirty second public release 1.4.4, 1.3.9, 1.2.7, 1.1.5
+## [1.4.4, 1.3.9, 1.2.7, 1.1.5] 2013-01-13
   - [SEC] Rack::Auth::AbstractRequest no longer symbolizes arbitrary strings
   - Fixed erroneous test case in the 1.3.x series
 
-### January 7th, 2013: Thirty first public release 1.4.3
+## [1.4.3] 2013-01-07
   - Security: Prevent unbounded reads in large multipart boundaries
 
-### January 7th, 2013: Thirtieth public release 1.3.8
+## [1.3.8] 2013-01-07
   - Security: Prevent unbounded reads in large multipart boundaries
 
-### January 6th, 2013: Twenty ninth public release 1.4.2
+## [1.4.2] 2013-01-06
   - Add warnings when users do not provide a session secret
   - Fix parsing performance for unquoted filenames
   - Updated URI backports
@@ -242,7 +176,7 @@ Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
   - Rack::BodyProxy now explicitly defines #each, useful for C extensions
   - Cookies that are not URI escaped no longer cause exceptions
 
-### January 6th, 2013: Twenty eighth public release 1.3.7
+## [1.3.7] 2013-01-06
   - Add warnings when users do not provide a session secret
   - Fix parsing performance for unquoted filenames
   - Updated URI backports
@@ -259,14 +193,14 @@ Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
   - Additional notes regarding ECMA escape compatibility issues
   - Fix the parsing of multiple ranges in range headers
 
-### January 6th, 2013: Twenty seventh public release 1.2.6
+## [1.2.6] 2013-01-06
   - Add warnings when users do not provide a session secret
   - Fix parsing performance for unquoted filenames
 
-### January 6th, 2013: Twenty sixth public release 1.1.4
+## [1.1.4] 2013-01-06
   - Add warnings when users do not provide a session secret
 
-### January 22nd, 2012: Twenty fifth public release 1.4.1
+## [1.4.1] 2012-01-22
   - Alter the keyspace limit calculations to reduce issues with nested params
   - Add a workaround for multipart parsing where files contain unescaped "%"
   - Added Rack::Response::Helpers#method_not_allowed? (code 405)
@@ -282,7 +216,7 @@ Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
   - Rack::Static no longer defaults to serving index files
   - Rack.release was fixed
 
-### December 28th, 2011: Twenty fourth public release 1.4.0
+## [1.4.0] 2011-12-28
   - Ruby 1.8.6 support has officially been dropped. Not all tests pass.
   - Raise sane error messages for broken config.ru
   - Allow combining run and map in a config.ru
@@ -301,32 +235,32 @@ Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
   - Support added for HTTP_X_FORWARDED_SCHEME
   - Numerous bug fixes, including many fixes for new and alternate rubies
 
-### December 28th, 2011: Twenty first public release: 1.1.3.
+## [1.1.3] 2011-12-28
   - Security fix. http://www.ocert.org/advisories/ocert-2011-003.html
     Further information here: http://jruby.org/2011/12/27/jruby-1-6-5-1
 
-### October 17, 2011: Twentieth public release 1.3.5
+## [1.3.5] 2011-10-17
   - Fix annoying warnings caused by the backport in 1.3.4
 
-### October 1, 2011: Nineteenth public release 1.3.4
+## [1.3.4] 2011-10-01
   - Backport security fix from 1.9.3, also fixes some roundtrip issues in URI
   - Small documentation update
   - Fix an issue where BodyProxy could cause an infinite recursion
   - Add some supporting files for travis-ci
 
-### September 16, 2011: Eighteenth public release 1.2.4
+## [1.2.4] 2011-09-16
   - Fix a bug with MRI regex engine to prevent XSS by malformed unicode
 
-### September 16, 2011: Seventeenth public release 1.3.3
+## [1.3.3] 2011-09-16
   - Fix bug with broken query parameters in Rack::ShowExceptions
   - Rack::Request#cookies no longer swallows exceptions on broken input
   - Prevents XSS attacks enabled by bug in Ruby 1.8's regexp engine
   - Rack::ConditionalGet handles broken If-Modified-Since helpers
 
-### July 16, 2011: Sixteenth public release 1.3.2
+## [1.3.2] 2011-07-16
   - Fix for Rails and rack-test, Rack::Utils#escape calls to_s
 
-### July 13, 2011: Fifteenth public release 1.3.1
+## [1.3.1] 2011-07-13
   - Fix 1.9.1 support
   - Fix JRuby support
   - Properly handle $KCODE in Rack::Utils.escape
@@ -337,11 +271,11 @@ Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
   - Rack::MockResponse calls close on the body object
   - Fix a DOS vector from MRI stdlib backport
 
-### May 22nd, 2011: Fourteenth public release 1.2.3
+## [1.2.3] 2011-05-22
   - Pulled in relevant bug fixes from 1.3
   - Fixed 1.8.6 support
 
-### May 22nd, 2011: Thirteenth public release 1.3.0
+## [1.3.0] 2011-05-22
   - Various performance optimizations
   - Various multipart fixes
   - Various multipart refactors
@@ -361,16 +295,16 @@ Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
   - Cookies respect renew
   - Session middleware uses SecureRandom.hex
 
-### March 13th, 2011: Twelfth public release 1.2.2/1.1.2.
+## [1.2.2, 1.1.2] 2011-03-13
   - Security fix in Rack::Auth::Digest::MD5: when authenticator
     returned nil, permission was granted on empty password.
 
-### June 15th, 2010: Eleventh public release 1.2.1.
+## [1.2.1] 2010-06-15
   - Make CGI handler rewindable
   - Rename spec/ to test/ to not conflict with SPEC on lesser
     operating systems
 
-### June 13th, 2010: Tenth public release 1.2.0.
+## [1.2.0] 2010-06-13
   - Removed Camping adapter: Camping 2.0 supports Rack as-is
   - Removed parsing of quoted values
   - Add Request.trace? and Request.options?
@@ -379,7 +313,7 @@ Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
   - Various multipart fixes
   - Switch test suite to bacon
 
-### January 3rd, 2010: Ninth public release 1.1.0.
+## [1.1.0] 2010-01-03
   - Moved Auth::OpenID to rack-contrib.
   - SPEC change that relaxes Lint slightly to allow subclasses of the
     required types
@@ -414,7 +348,7 @@ Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
   - Enforce binary encoding in RewindableInput
   - Set correct external_encoding for handlers that don't use RewindableInput
 
-### October 18th, 2009: Eighth public release 1.0.1.
+## [1.0.1] 2009-10-18
   - Bump remainder of rack.versions.
   - Support the pure Ruby FCGI implementation.
   - Fix for form names containing "=": split first then unescape components
@@ -425,7 +359,7 @@ Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
   - Make sure WEBrick respects the :Host option
   - Many Ruby 1.9 fixes.
 
-### April 25th, 2009: Seventh public release 1.0.0.
+## [1.0.0] 2009-04-25
   - SPEC change: Rack::VERSION has been pushed to [1,0].
   - SPEC change: header values must be Strings now, split on "\n".
   - SPEC change: Content-Length can be missing, in this case chunked transfer
@@ -447,10 +381,10 @@ Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
   - The Rakefile has been rewritten.
   - Many bugfixes and small improvements.
 
-### January 9th, 2009: Sixth public release 0.9.1.
+## [0.9.1] 2009-01-09
   - Fix directory traversal exploits in Rack::File and Rack::Directory.
 
-### January 6th, 2009: Fifth public release 0.9.
+## [0.9] 2009-01-06
   - Rack is now managed by the Rack Core Team.
   - Rack::Lint is stricter and follows the HTTP RFCs more closely.
   - Added ConditionalGet middleware.
@@ -466,7 +400,7 @@ Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
   - Made HeaderHash case-preserving.
   - Many bugfixes and small improvements.
 
-### August 21st, 2008: Fourth public release 0.4.
+## [0.4] 2008-08-21
   - New middleware, Rack::Deflater, by Christoffer Sawicki.
   - OpenID authentication now needs ruby-openid 2.
   - New Memcache sessions, by blink.
@@ -478,7 +412,7 @@ Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
   - Improved tests.
   - Rack moved to Git.
 
-### February 26th, 2008: Third public release 0.3.
+## [0.3] 2008-02-26
   - LiteSpeed handler, by Adrian Madrid.
   - SCGI handler, by Jeremy Evans.
   - Pool sessions, by blink.
@@ -490,7 +424,7 @@ Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
   - HTTP status 201 can contain a Content-Type and a body now.
   - Many bugfixes, especially related to Cookie handling.
 
-### May 16th, 2007: Second public release 0.2.
+## [0.2] 2007-05-16
   - HTTP Basic authentication.
   - Cookie Sessions.
   - Static file handler.
@@ -500,6 +434,4 @@ Fri Jun 12 11:37:41 2015  Aaron Patterson <tenderlove@ruby-lang.org>
   - Bug fixes in the Camping adapter.
   - Removed Rails adapter, was too alpha.
 
-### March 3rd, 2007: First public release 0.1.
-
-/* vim: set filetype=changelog */
+## [0.1] 2007-03-03
diff --git a/Gemfile b/Gemfile
index 2a767746..b9d9d748 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 source 'https://rubygems.org'
 
 gemspec
@@ -10,12 +12,18 @@ c_platforms = Bundler::Dsl::VALID_PLATFORMS.dup.delete_if do |platform|
   platform =~ /jruby/
 end
 
+gem "rubocop", "0.68.1", require: false
+
 # Alternative solution that might work, but it has bad interactions with
 # Gemfile.lock if that gets committed/reused:
 # c_platforms = [:mri] if Gem.platforms.last.os == "java"
 
 group :extra do
-  gem 'fcgi', :platforms => c_platforms
+  gem 'fcgi', platforms: c_platforms
   gem 'memcache-client'
-  gem 'thin', :platforms => c_platforms
+  gem 'thin', platforms: c_platforms
+end
+
+group :doc do
+  gem 'rdoc'
 end
diff --git a/COPYING b/MIT-LICENSE
index 1f5c7013..703d118f 100644
--- a/COPYING
+++ b/MIT-LICENSE
@@ -1,4 +1,6 @@
-Copyright (c) 2007-2016 Christian Neukirchen <purl.org/net/chneukirchen>
+The MIT License (MIT)
+
+Copyright (C) 2007-2019 Leah Neukirchen <http://leahneukirchen.org/infopage.html>
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to
@@ -13,6 +15,6 @@ all copies or substantial portions of the Software.
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/NEWS b/NEWS
deleted file mode 100644
index a643ddb9..00000000
--- a/NEWS
+++ /dev/null
@@ -1,14 +0,0 @@
-# NEWS for Rack 2.0.0
-
-This document is a list of user visible feature changes made between
-releases except for bug fixes.
-
-Note that each entry is kept so brief that no reason behind or
-reference information is supplied with.  For a full list of changes
-with all sufficient information, see the HISTORY.md file or the commit log.
-
-## Changes Since 1.6
-
-* Rack::Session::Abstract::ID is deprecated.  Please change to use
-Rack::Session::Abstract::Persisted
-
diff --git a/README.rdoc b/README.rdoc
index bedcda99..883380c1 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -1,17 +1,24 @@
-= Rack, a modular Ruby webserver interface {<img src="https://secure.travis-ci.org/rack/rack.svg" alt="Build Status" />}[http://travis-ci.org/rack/rack] {<img src="https://gemnasium.com/rack/rack.svg" alt="Dependency Status" />}[https://gemnasium.com/rack/rack]
+= \Rack, a modular Ruby webserver interface
 
-Rack provides a minimal, modular, and adaptable interface for developing
-web applications in Ruby.  By wrapping HTTP requests and responses in
+{<img src="https://rack.github.io/rack-logo.png" width="400" alt="rack powers web applications" />}[https://rack.github.io/]
+
+{<img src="https://circleci.com/gh/rack/rack.svg?style=svg" alt="CircleCI" />}[https://circleci.com/gh/rack/rack]
+{<img src="https://badge.fury.io/rb/rack.svg" alt="Gem Version" />}[http://badge.fury.io/rb/rack]
+{<img src="https://api.dependabot.com/badges/compatibility_score?dependency-name=rack&package-manager=bundler&version-scheme=semver" alt="SemVer Stability" />}[https://dependabot.com/compatibility-score.html?dependency-name=rack&package-manager=bundler&version-scheme=semver]
+
+\Rack provides a minimal, modular, and adaptable interface for developing
+web applications in Ruby. By wrapping HTTP requests and responses in
 the simplest way possible, it unifies and distills the API for web
 servers, web frameworks, and software in between (the so-called
 middleware) into a single method call.
 
-The exact details of this are described in the Rack specification,
-which all Rack applications should conform to.
+The exact details of this are described in the \Rack specification,
+which all \Rack applications should conform to.
 
 == Supported web servers
 
-The included *handlers* connect all kinds of web servers to Rack:
+The included *handlers* connect all kinds of web servers to \Rack:
+
 * WEBrick
 * FCGI
 * CGI
@@ -19,31 +26,34 @@ The included *handlers* connect all kinds of web servers to Rack:
 * LiteSpeed
 * Thin
 
-These web servers include Rack handlers in their distributions:
+These web servers include \Rack handlers in their distributions:
+
+* Agoo
 * Ebb
 * Fuzed
 * Glassfish v3
 * Phusion Passenger (which is mod_rack for Apache and for nginx)
 * Puma
 * Reel
-* Unicorn
 * unixrack
 * uWSGI
-* yahns
 
-Any valid Rack app will run the same on all these handlers, without
+Any valid \Rack app will run the same on all these handlers, without
 changing anything.
 
 == Supported web frameworks
 
-These frameworks include Rack adapters in their distributions:
+These frameworks include \Rack adapters in their distributions:
+
 * Camping
 * Coset
 * Espresso
 * Halcyon
+* Hanami
 * Mack
 * Maveric
 * Merb
+* Padrino
 * Racktools::SimpleApplication
 * Ramaze
 * Ruby on Rails
@@ -51,14 +61,16 @@ These frameworks include Rack adapters in their distributions:
 * Sinatra
 * Sin
 * Vintage
+* WABuR
 * Waves
 * Wee
 * ... and many others.
 
 == Available middleware
 
-Between the server and the framework, Rack can be customized to your
+Between the server and the framework, \Rack can be customized to your
 applications needs using middleware, for example:
+
 * Rack::URLMap, to route to multiple applications inside the same process.
 * Rack::CommonLogger, for creating Apache-style logfiles.
 * Rack::ShowException, for catching unhandled exceptions and
@@ -67,33 +79,34 @@ applications needs using middleware, for example:
 * ...many others!
 
 All these components use the same interface, which is described in
-detail in the Rack specification.  These optional components can be
+detail in the \Rack specification.  These optional components can be
 used in any way you wish.
 
 == Convenience
 
 If you want to develop outside of existing frameworks, implement your
-own ones, or develop middleware, Rack provides many helpers to create
-Rack applications quickly and without doing the same web stuff all
+own ones, or develop middleware, \Rack provides many helpers to create
+\Rack applications quickly and without doing the same web stuff all
 over:
+
 * Rack::Request, which also provides query string parsing and
   multipart handling.
 * Rack::Response, for convenient generation of HTTP replies and
   cookie handling.
 * Rack::MockRequest and Rack::MockResponse for efficient and quick
-  testing of Rack application without real HTTP round-trips.
+  testing of \Rack application without real HTTP round-trips.
 
 == rack-contrib
 
 The plethora of useful middleware created the need for a project that
-collects fresh Rack middleware.  rack-contrib includes a variety of
-add-on components for Rack and it is easy to contribute new modules.
+collects fresh \Rack middleware.  rack-contrib includes a variety of
+add-on components for \Rack and it is easy to contribute new modules.
 
 * https://github.com/rack/rack-contrib
 
 == rackup
 
-rackup is a useful tool for running Rack applications, which uses the
+rackup is a useful tool for running \Rack applications, which uses the
 Rack::Builder DSL to configure middleware and build up applications
 easily.
 
@@ -117,18 +130,13 @@ By default, the lobster is found at http://localhost:9292.
 
 == Installing with RubyGems
 
-A Gem of Rack is available at rubygems.org.  You can install it with:
+A Gem of \Rack is available at {rubygems.org}[https://rubygems.org/gems/rack]. You can install it with:
 
     gem install rack
 
-I also provide a local mirror of the gems (and development snapshots)
-at my site:
-
-    gem install rack --source http://chneukirchen.org/releases/gems/
-
 == Running the tests
 
-Testing Rack requires the bacon testing framework:
+Testing \Rack requires the bacon testing framework:
 
     bundle install --without extra # to be able to run the fast tests
 
@@ -138,7 +146,7 @@ Or:
 
 There is a rake-based test task:
 
-    rake test       tests all the tests
+    rake test # tests all the tests
 
 The testsuite has no dependencies outside of the core Ruby
 installation and bacon.
@@ -159,9 +167,9 @@ Download and install lighttpd:
 
 Installing the FCGI libraries:
 
-    curl -O http://www.fastcgi.com/dist/fcgi-2.4.0.tar.gz
-    tar xzvf fcgi-2.4.0.tar.gz
-    cd fcgi-2.4.0
+    curl -O -k -L https://github.com/FastCGI-Archives/FastCGI.com/raw/master/original_snapshot/fcgi-2.4.1-SNAP-0910052249.tar.gz
+    tar xvfz fcgi-2.4.1-SNAP-0910052249.tar.gz
+    cd fcgi-2.4.1-SNAP-0910052249
     ./configure --prefix=/usr/local
     make
     sudo make install
@@ -176,7 +184,7 @@ run on port 11211) and memcache-client installed.
 
 == Configuration
 
-Several parameters can be modified on Rack::Utils to configure Rack behaviour.
+Several parameters can be modified on Rack::Utils to configure \Rack behaviour.
 
 e.g:
 
@@ -198,27 +206,28 @@ The default is 128, which means that a single request can't upload more than 128
 
 Set to 0 for no limit.
 
-Can also be set via the RACK_MULTIPART_PART_LIMIT environment variable.
+Can also be set via the +RACK_MULTIPART_PART_LIMIT+ environment variable.
 
-== History
+== Changelog
 
-See <https://github.com/rack/rack/blob/master/HISTORY.md>.
+See {CHANGELOG.md}[https://github.com/rack/rack/blob/master/CHANGELOG.md].
 
 == Contact
 
 Please post bugs, suggestions and patches to
-the bug tracker at <https://github.com/rack/rack/issues>.
+the bug tracker at {issues}[https://github.com/rack/rack/issues].
 
 Please post security related bugs and suggestions to the core team at
-<https://groups.google.com/group/rack-core> or rack-core@googlegroups.com. This
+<https://groups.google.com/forum/#!forum/rack-core> or rack-core@googlegroups.com. This
 list is not public. Due to wide usage of the library, it is strongly preferred
 that we manage timing in order to provide viable patches at the time of
 disclosure. Your assistance in this matter is greatly appreciated.
 
 Mailing list archives are available at
-<https://groups.google.com/group/rack-devel>.
+<https://groups.google.com/forum/#!forum/rack-devel>.
 
 Git repository (send Git patches to the mailing list):
+
 * https://github.com/rack/rack
 * http://git.vuxu.org/cgi-bin/gitweb.cgi?p=rack-github.git
 
@@ -226,9 +235,9 @@ You are also welcome to join the #rack channel on irc.freenode.net.
 
 == Thanks
 
-The Rack Core Team, consisting of
+The \Rack Core Team, consisting of
 
-* Christian Neukirchen (chneukirchen[https://github.com/chneukirchen])
+* Leah Neukirchen (leahneukirchen[https://github.com/leahneukirchen])
 * James Tucker (raggi[https://github.com/raggi])
 * Josh Peek (josh[https://github.com/josh])
 * José Valim (josevalim[https://github.com/josevalim])
@@ -237,7 +246,7 @@ The Rack Core Team, consisting of
 * Santiago Pastorino (spastorino[https://github.com/spastorino])
 * Konstantin Haase (rkh[https://github.com/rkh])
 
-and the Rack Alumnis
+and the \Rack Alumnis
 
 * Ryan Tomayko (rtomayko[https://github.com/rtomayko])
 * Scytrin dai Kinthra (scytrin[https://github.com/scytrin])
@@ -269,36 +278,16 @@ would like to thank:
 * Alexander Kellett for testing the Gem and reviewing the announcement.
 * Marcus Rückert, for help with configuring and debugging lighttpd.
 * The WSGI team for the well-done and documented work they've done and
-  Rack builds up on.
+  \Rack builds up on.
 * All bug reporters and patch contributors not mentioned above.
 
-== Copyright
-
-Copyright (C) 2007, 2008, 2009, 2010 Christian Neukirchen <http://purl.org/net/chneukirchen>
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to
-deal in the Software without restriction, including without limitation the
-rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-sell copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
 == Links
 
-Rack:: <http://rack.github.io/>
-Official Rack repositories:: <https://github.com/rack>
-Rack Bug Tracking:: <https://github.com/rack/rack/issues>
-rack-devel mailing list:: <https://groups.google.com/group/rack-devel>
-Rack's Rubyforge project:: <http://rubyforge.org/projects/rack>
+\Rack:: <https://rack.github.io/>
+Official \Rack repositories:: <https://github.com/rack>
+\Rack Bug Tracking:: <https://github.com/rack/rack/issues>
+rack-devel mailing list:: <https://groups.google.com/forum/#!forum/rack-devel>
+
+== License
 
-Christian Neukirchen:: <http://chneukirchen.org/>
+\Rack is released under the {MIT License}[https://opensource.org/licenses/MIT].
diff --git a/Rakefile b/Rakefile
index c112f1da..a365ff54 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,7 +1,9 @@
-# Rakefile for Rack.  -*-ruby-*-
+# frozen_string_literal: true
+
+require "rake/testtask"
 
 desc "Run all the tests"
-task :default => [:test]
+task default: :test
 
 desc "Install gem dependencies"
 task :deps do
@@ -16,7 +18,7 @@ task :deps do
 end
 
 desc "Make an archive as .tar.gz"
-task :dist => %w[chmod ChangeLog SPEC rdoc] do
+task dist: %w[chmod changelog spec rdoc] do
   sh "git archive --format=tar --prefix=#{release}/ HEAD^{tree} >#{release}.tar"
   sh "pax -waf #{release}.tar -s ':^:#{release}/:' SPEC ChangeLog doc rack.gemspec"
   sh "gzip -f -9 #{release}.tar"
@@ -31,7 +33,7 @@ task :officialrelease do
   sh "mv stage/#{release}.tar.gz stage/#{release}.gem ."
 end
 
-task :officialrelease_really => %w[SPEC dist gem] do
+task officialrelease_really: %w[spec dist gem] do
   sh "shasum #{release}.tar.gz #{release}.gem"
 end
 
@@ -46,7 +48,7 @@ task :chmod do
 end
 
 desc "Generate a ChangeLog"
-task :changelog => %w[ChangeLog]
+task changelog: "ChangeLog"
 
 file '.git/index'
 file "ChangeLog" => '.git/index' do
@@ -68,8 +70,10 @@ file "ChangeLog" => '.git/index' do
   }
 end
 
-file 'lib/rack/lint.rb'
 desc "Generate Rack Specification"
+task spec: "SPEC"
+
+file 'lib/rack/lint.rb'
 file "SPEC" => 'lib/rack/lint.rb' do
   File.open("SPEC", "wb") { |file|
     IO.foreach("lib/rack/lint.rb") { |line|
@@ -80,24 +84,27 @@ file "SPEC" => 'lib/rack/lint.rb' do
   }
 end
 
-desc "Run all the fast + platform agnostic tests"
-task :test => 'SPEC' do
-  opts     = ENV['TEST'] || ''
-  specopts = ENV['TESTOPTS']
-
-  sh "ruby -I./lib:./test -S minitest #{opts} #{specopts} test/gemloader.rb test/spec*.rb"
+Rake::TestTask.new("test:regular") do |t|
+  t.libs << "test"
+  t.test_files = FileList["test/**/*_test.rb", "test/**/spec_*.rb", "test/gemloader.rb"]
+  t.warning = false
+  t.verbose = true
 end
 
+desc "Run all the fast + platform agnostic tests"
+task test: %w[spec test:regular]
+
 desc "Run all the tests we run on CI"
-task :ci => :test
+task ci: :test
 
-task :gem => ["SPEC"] do
+task gem: :spec do
   sh "gem build rack.gemspec"
 end
 
-task :doc => :rdoc
+task doc: :rdoc
+
 desc "Generate RDoc documentation"
-task :rdoc => %w[ChangeLog SPEC] do
+task rdoc: %w[changelog spec] do
   sh(*%w{rdoc --line-numbers --main README.rdoc
               --title 'Rack\ Documentation' --charset utf-8 -U -o doc} +
               %w{README.rdoc KNOWN-ISSUES SPEC ChangeLog} +
@@ -105,11 +112,11 @@ task :rdoc => %w[ChangeLog SPEC] do
   cp "contrib/rdoc.css", "doc/rdoc.css"
 end
 
-task :pushdoc => %w[rdoc] do
+task pushdoc: :rdoc do
   sh "rsync -avz doc/ rack.rubyforge.org:/var/www/gforge-projects/rack/doc/"
 end
 
-task :pushsite => %w[pushdoc] do
+task pushsite: :pushdoc do
   sh "cd site && git gc"
   sh "rsync -avz site/ rack.rubyforge.org:/var/www/gforge-projects/rack/"
   sh "cd site && git push"
diff --git a/SECURITY_POLICY.md b/SECURITY_POLICY.md
index 844d6969..04fdd488 100644
--- a/SECURITY_POLICY.md
+++ b/SECURITY_POLICY.md
@@ -10,22 +10,22 @@ New features will only be added to the master branch and will not be made availa
 
 Only the latest release series will receive bug fixes. When enough bugs are fixed and its deemed worthy to release a new gem, this is the branch it happens from.
 
-* Current release series: 1.6.x
+* Current release series: 2.0.x
 
 ### Security issues
 
 The current release series and the next most recent one will receive patches and new versions in case of a security issue.
 
-* Current release series: 1.6.x
-* Next most recent release series: 1.5.x
+* Current release series: 2.0.x
+* Next most recent release series: 1.6.x
 
 ### Severe security issues
 
 For severe security issues we will provide new versions as above, and also the last major release series will receive patches and new versions. The classification of the security issue is judged by the core team.
 
-* Current release series: 1.6.x
-* Next most recent release series: 1.5.x
-* Last most recent release series: 1.4.x
+* Current release series: 2.0.x
+* Next most recent release series: 1.6.x
+* Last most recent release series: 1.5.x
 
 ### Unsupported Release Series
 
@@ -33,14 +33,13 @@ When a release series is no longer supported, it’s your own responsibility to
 
 ## Reporting a bug
 
-All security bugs in Rack should be reported to the core team through our private mailing list [rack-core@googlegroups.com](https://groups.google.com/group/rack-core). Your report will be acknowledged within 24 hours, and you’ll receive a more detailed response to your email within 48 hours indicating the next steps in handling your report.
+All security bugs in Rack should be reported to the core team through our private mailing list [rack-core@googlegroups.com](https://groups.google.com/forum/#!forum/rack-core). Your report will be acknowledged within 24 hours, and you’ll receive a more detailed response to your email within 48 hours indicating the next steps in handling your report.
 
 After the initial reply to your report the security team will endeavor to keep you informed of the progress being made towards a fix and full announcement. These updates will be sent at least every five days, in reality this is more likely to be every 24-48 hours.
 
 If you have not received a reply to your email within 48 hours, or have not heard from the security team for the past five days there are a few steps you can take:
 
 * Contact the current security coordinator [Aaron Patterson](mailto:tenderlove@ruby-lang.org) directly
-* Contact the back-up contact [Santiago Pastorino](mailto:santiago@wyeworks.com) directly.
 
 ## Disclosure Policy
 
@@ -64,4 +63,4 @@ No one outside the core team, the initial reporter or vendor-sec will be notifie
 
 ## Comments on this Policy
 
-If you have any suggestions to improve this policy, please send an email the core team at [rack-core@googlegroups.com](https://groups.google.com/group/rack-core).
+If you have any suggestions to improve this policy, please send an email the core team at [rack-core@googlegroups.com](https://groups.google.com/forum/#!forum/rack-core).
diff --git a/SPEC b/SPEC
index 7e3af40a..3352b843 100644
--- a/SPEC
+++ b/SPEC
@@ -60,9 +60,8 @@ below.
                            the presence or absence of the
                            appropriate HTTP header in the
                            request. See
-                           {https://tools.ietf.org/html/rfc3875#section-4.1.18
-                           RFC3875 section 4.1.18} for
-                           specific behavior.
+                           {RFC3875 section 4.1.18}[https://tools.ietf.org/html/rfc3875#section-4.1.18]
+                           for specific behavior.
 In addition to this, the Rack environment must include these
 Rack-specific variables:
 <tt>rack.version</tt>:: The Array representing this version of Rack
@@ -98,12 +97,13 @@ Rack-specific variables:
 Additional environment specifications have approved to
 standardized middleware APIs.  None of these are required to
 be implemented by the server.
-<tt>rack.session</tt>:: A hash like interface for storing request session data.
+<tt>rack.session</tt>:: A hash like interface for storing
+                        request session data.
                         The store must implement:
-                         store(key, value)         (aliased as []=);
-                         fetch(key, default = nil) (aliased as []);
-                         delete(key);
-                         clear;
+                        store(key, value)         (aliased as []=);
+                        fetch(key, default = nil) (aliased as []);
+                        delete(key);
+                        clear;
 <tt>rack.logger</tt>:: A common object interface for logging messages.
                        The object must implement:
                         info(message, &block)
@@ -237,10 +237,10 @@ consisting of lines (for multiple header values, e.g. multiple
 The lines must not contain characters below 037.
 === The Content-Type
 There must not be a <tt>Content-Type</tt>, when the +Status+ is 1xx,
-204, 205 or 304.
+204 or 304.
 === The Content-Length
 There must not be a <tt>Content-Length</tt> header when the
-+Status+ is 1xx, 204, 205 or 304.
++Status+ is 1xx, 204 or 304.
 === The Body
 The Body must respond to +each+
 and must only yield String values.
diff --git a/bin/rackup b/bin/rackup
index ad94af4b..58988a0b 100755
--- a/bin/rackup
+++ b/bin/rackup
@@ -1,4 +1,5 @@
 #!/usr/bin/env ruby
+# frozen_string_literal: true
 
 require "rack"
 Rack::Server.start
diff --git a/circle.yml b/circle.yml
deleted file mode 100644
index 08e2b358..00000000
--- a/circle.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-dependencies:
-  pre:
-    - sudo apt-get install lighttpd libfcgi-dev libmemcache-dev memcached
-machine:
-  ruby:
-    version: ruby-head
diff --git a/example/lobster.ru b/example/lobster.ru
index cc7ffcae..901e18a5 100644
--- a/example/lobster.ru
+++ b/example/lobster.ru
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/lobster'
 
 use Rack::ShowExceptions
diff --git a/example/protectedlobster.rb b/example/protectedlobster.rb
index 26b23661..fe4f0b09 100644
--- a/example/protectedlobster.rb
+++ b/example/protectedlobster.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack'
 require 'rack/lobster'
 
@@ -11,4 +13,4 @@ protected_lobster.realm = 'Lobster 2.0'
 
 pretty_protected_lobster = Rack::ShowStatus.new(Rack::ShowExceptions.new(protected_lobster))
 
-Rack::Server.start :app => pretty_protected_lobster, :Port => 9292
+Rack::Server.start app: pretty_protected_lobster, Port: 9292
diff --git a/example/protectedlobster.ru b/example/protectedlobster.ru
index 1ba48702..0eb243cc 100644
--- a/example/protectedlobster.ru
+++ b/example/protectedlobster.ru
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/lobster'
 
 use Rack::ShowExceptions
diff --git a/lib/rack.rb b/lib/rack.rb
index 00621178..5a8d71c4 100644
--- a/lib/rack.rb
+++ b/lib/rack.rb
@@ -1,7 +1,9 @@
-# Copyright (C) 2007, 2008, 2009, 2010 Christian Neukirchen <purl.org/net/chneukirchen>
+# frozen_string_literal: true
+
+# Copyright (C) 2007-2019 Leah Neukirchen <http://leahneukirchen.org/infopage.html>
 #
 # Rack is freely distributable under the terms of an MIT-style license.
-# See COPYING or http://www.opensource.org/licenses/mit-license.php.
+# See MIT-LICENSE or https://opensource.org/licenses/MIT.
 
 # The Rack main module, serving as a namespace for all core Rack
 # modules and classes.
@@ -11,80 +13,80 @@
 
 module Rack
   # The Rack protocol version number implemented.
-  VERSION = [1,3]
+  VERSION = [1, 3]
 
   # Return the Rack protocol version as a dotted string.
   def self.version
     VERSION.join(".")
   end
 
-  RELEASE = "2.0.0.rc1"
+  RELEASE = "2.1.0"
 
   # Return the Rack release as a dotted string.
   def self.release
     RELEASE
   end
 
-  HTTP_HOST         = 'HTTP_HOST'.freeze
-  HTTP_VERSION      = 'HTTP_VERSION'.freeze
-  HTTPS             = 'HTTPS'.freeze
-  PATH_INFO         = 'PATH_INFO'.freeze
-  REQUEST_METHOD    = 'REQUEST_METHOD'.freeze
-  REQUEST_PATH      = 'REQUEST_PATH'.freeze
-  SCRIPT_NAME       = 'SCRIPT_NAME'.freeze
-  QUERY_STRING      = 'QUERY_STRING'.freeze
-  SERVER_PROTOCOL   = 'SERVER_PROTOCOL'.freeze
-  SERVER_NAME       = 'SERVER_NAME'.freeze
-  SERVER_ADDR       = 'SERVER_ADDR'.freeze
-  SERVER_PORT       = 'SERVER_PORT'.freeze
-  CACHE_CONTROL     = 'Cache-Control'.freeze
-  CONTENT_LENGTH    = 'Content-Length'.freeze
-  CONTENT_TYPE      = 'Content-Type'.freeze
-  SET_COOKIE        = 'Set-Cookie'.freeze
-  TRANSFER_ENCODING = 'Transfer-Encoding'.freeze
-  HTTP_COOKIE       = 'HTTP_COOKIE'.freeze
-  ETAG              = 'ETag'.freeze
+  HTTP_HOST         = 'HTTP_HOST'
+  HTTP_VERSION      = 'HTTP_VERSION'
+  HTTPS             = 'HTTPS'
+  PATH_INFO         = 'PATH_INFO'
+  REQUEST_METHOD    = 'REQUEST_METHOD'
+  REQUEST_PATH      = 'REQUEST_PATH'
+  SCRIPT_NAME       = 'SCRIPT_NAME'
+  QUERY_STRING      = 'QUERY_STRING'
+  SERVER_PROTOCOL   = 'SERVER_PROTOCOL'
+  SERVER_NAME       = 'SERVER_NAME'
+  SERVER_ADDR       = 'SERVER_ADDR'
+  SERVER_PORT       = 'SERVER_PORT'
+  CACHE_CONTROL     = 'Cache-Control'
+  CONTENT_LENGTH    = 'Content-Length'
+  CONTENT_TYPE      = 'Content-Type'
+  SET_COOKIE        = 'Set-Cookie'
+  TRANSFER_ENCODING = 'Transfer-Encoding'
+  HTTP_COOKIE       = 'HTTP_COOKIE'
+  ETAG              = 'ETag'
 
   # HTTP method verbs
-  GET     = 'GET'.freeze
-  POST    = 'POST'.freeze
-  PUT     = 'PUT'.freeze
-  PATCH   = 'PATCH'.freeze
-  DELETE  = 'DELETE'.freeze
-  HEAD    = 'HEAD'.freeze
-  OPTIONS = 'OPTIONS'.freeze
-  LINK    = 'LINK'.freeze
-  UNLINK  = 'UNLINK'.freeze
-  TRACE   = 'TRACE'.freeze
+  GET     = 'GET'
+  POST    = 'POST'
+  PUT     = 'PUT'
+  PATCH   = 'PATCH'
+  DELETE  = 'DELETE'
+  HEAD    = 'HEAD'
+  OPTIONS = 'OPTIONS'
+  LINK    = 'LINK'
+  UNLINK  = 'UNLINK'
+  TRACE   = 'TRACE'
 
   # Rack environment variables
-  RACK_VERSION                        = 'rack.version'.freeze
-  RACK_TEMPFILES                      = 'rack.tempfiles'.freeze
-  RACK_ERRORS                         = 'rack.errors'.freeze
-  RACK_LOGGER                         = 'rack.logger'.freeze
-  RACK_INPUT                          = 'rack.input'.freeze
-  RACK_SESSION                        = 'rack.session'.freeze
-  RACK_SESSION_OPTIONS                = 'rack.session.options'.freeze
-  RACK_SHOWSTATUS_DETAIL              = 'rack.showstatus.detail'.freeze
-  RACK_MULTITHREAD                    = 'rack.multithread'.freeze
-  RACK_MULTIPROCESS                   = 'rack.multiprocess'.freeze
-  RACK_RUNONCE                        = 'rack.run_once'.freeze
-  RACK_URL_SCHEME                     = 'rack.url_scheme'.freeze
-  RACK_HIJACK                         = 'rack.hijack'.freeze
-  RACK_IS_HIJACK                      = 'rack.hijack?'.freeze
-  RACK_HIJACK_IO                      = 'rack.hijack_io'.freeze
-  RACK_RECURSIVE_INCLUDE              = 'rack.recursive.include'.freeze
-  RACK_MULTIPART_BUFFER_SIZE          = 'rack.multipart.buffer_size'.freeze
-  RACK_MULTIPART_TEMPFILE_FACTORY     = 'rack.multipart.tempfile_factory'.freeze
-  RACK_REQUEST_FORM_INPUT             = 'rack.request.form_input'.freeze
-  RACK_REQUEST_FORM_HASH              = 'rack.request.form_hash'.freeze
-  RACK_REQUEST_FORM_VARS              = 'rack.request.form_vars'.freeze
-  RACK_REQUEST_COOKIE_HASH            = 'rack.request.cookie_hash'.freeze
-  RACK_REQUEST_COOKIE_STRING          = 'rack.request.cookie_string'.freeze
-  RACK_REQUEST_QUERY_HASH             = 'rack.request.query_hash'.freeze
-  RACK_REQUEST_QUERY_STRING           = 'rack.request.query_string'.freeze
-  RACK_METHODOVERRIDE_ORIGINAL_METHOD = 'rack.methodoverride.original_method'.freeze
-  RACK_SESSION_UNPACKED_COOKIE_DATA   = 'rack.session.unpacked_cookie_data'.freeze
+  RACK_VERSION                        = 'rack.version'
+  RACK_TEMPFILES                      = 'rack.tempfiles'
+  RACK_ERRORS                         = 'rack.errors'
+  RACK_LOGGER                         = 'rack.logger'
+  RACK_INPUT                          = 'rack.input'
+  RACK_SESSION                        = 'rack.session'
+  RACK_SESSION_OPTIONS                = 'rack.session.options'
+  RACK_SHOWSTATUS_DETAIL              = 'rack.showstatus.detail'
+  RACK_MULTITHREAD                    = 'rack.multithread'
+  RACK_MULTIPROCESS                   = 'rack.multiprocess'
+  RACK_RUNONCE                        = 'rack.run_once'
+  RACK_URL_SCHEME                     = 'rack.url_scheme'
+  RACK_HIJACK                         = 'rack.hijack'
+  RACK_IS_HIJACK                      = 'rack.hijack?'
+  RACK_HIJACK_IO                      = 'rack.hijack_io'
+  RACK_RECURSIVE_INCLUDE              = 'rack.recursive.include'
+  RACK_MULTIPART_BUFFER_SIZE          = 'rack.multipart.buffer_size'
+  RACK_MULTIPART_TEMPFILE_FACTORY     = 'rack.multipart.tempfile_factory'
+  RACK_REQUEST_FORM_INPUT             = 'rack.request.form_input'
+  RACK_REQUEST_FORM_HASH              = 'rack.request.form_hash'
+  RACK_REQUEST_FORM_VARS              = 'rack.request.form_vars'
+  RACK_REQUEST_COOKIE_HASH            = 'rack.request.cookie_hash'
+  RACK_REQUEST_COOKIE_STRING          = 'rack.request.cookie_string'
+  RACK_REQUEST_QUERY_HASH             = 'rack.request.query_hash'
+  RACK_REQUEST_QUERY_STRING           = 'rack.request.query_string'
+  RACK_METHODOVERRIDE_ORIGINAL_METHOD = 'rack.methodoverride.original_method'
+  RACK_SESSION_UNPACKED_COOKIE_DATA   = 'rack.session.unpacked_cookie_data'
 
   autoload :Builder, "rack/builder"
   autoload :BodyProxy, "rack/body_proxy"
diff --git a/lib/rack/auth/abstract/handler.rb b/lib/rack/auth/abstract/handler.rb
index c657691e..3ed87091 100644
--- a/lib/rack/auth/abstract/handler.rb
+++ b/lib/rack/auth/abstract/handler.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Rack
   module Auth
     # Rack::Auth::AbstractHandler implements common authentication functionality.
@@ -8,7 +10,7 @@ module Rack
 
       attr_accessor :realm
 
-      def initialize(app, realm=nil, &authenticator)
+      def initialize(app, realm = nil, &authenticator)
         @app, @realm, @authenticator = app, realm, authenticator
       end
 
diff --git a/lib/rack/auth/abstract/request.rb b/lib/rack/auth/abstract/request.rb
index 80d1c272..23da4bf2 100644
--- a/lib/rack/auth/abstract/request.rb
+++ b/lib/rack/auth/abstract/request.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/request'
 
 module Rack
@@ -13,7 +15,11 @@ module Rack
       end
 
       def provided?
-        !authorization_key.nil?
+        !authorization_key.nil? && valid?
+      end
+
+      def valid?
+        !@env[authorization_key].nil?
       end
 
       def parts
diff --git a/lib/rack/auth/basic.rb b/lib/rack/auth/basic.rb
index 9c589214..95bbafc4 100644
--- a/lib/rack/auth/basic.rb
+++ b/lib/rack/auth/basic.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/auth/abstract/handler'
 require 'rack/auth/abstract/request'
 
@@ -45,7 +47,7 @@ module Rack
         end
 
         def credentials
-          @credentials ||= params.unpack("m*").first.split(/:/, 2)
+          @credentials ||= params.unpack("m*").first.split(':', 2)
         end
 
         def username
diff --git a/lib/rack/auth/digest/md5.rb b/lib/rack/auth/digest/md5.rb
index ddee35de..62bff984 100644
--- a/lib/rack/auth/digest/md5.rb
+++ b/lib/rack/auth/digest/md5.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/auth/abstract/handler'
 require 'rack/auth/digest/request'
 require 'rack/auth/digest/params'
@@ -21,7 +23,7 @@ module Rack
 
         attr_writer :passwords_hashed
 
-        def initialize(app, realm=nil, opaque=nil, &authenticator)
+        def initialize(app, realm = nil, opaque = nil, &authenticator)
           @passwords_hashed = nil
           if opaque.nil? and realm.respond_to? :values_at
             realm, opaque, @passwords_hashed = realm.values_at :realm, :opaque, :passwords_hashed
@@ -47,7 +49,7 @@ module Rack
 
           if valid?(auth)
             if auth.nonce.stale?
-              return unauthorized(challenge(:stale => true))
+              return unauthorized(challenge(stale: true))
             else
               env['REMOTE_USER'] = auth.username
 
@@ -61,7 +63,7 @@ module Rack
 
         private
 
-        QOP = 'auth'.freeze
+        QOP = 'auth'
 
         def params(hash = {})
           Params.new do |params|
@@ -106,21 +108,21 @@ module Rack
         alias :H :md5
 
         def KD(secret, data)
-          H([secret, data] * ':')
+          H "#{secret}:#{data}"
         end
 
         def A1(auth, password)
-          [ auth.username, auth.realm, password ] * ':'
+          "#{auth.username}:#{auth.realm}:#{password}"
         end
 
         def A2(auth)
-          [ auth.method, auth.uri ] * ':'
+          "#{auth.method}:#{auth.uri}"
         end
 
         def digest(auth, password)
           password_hash = passwords_hashed? ? password : H(A1(auth, password))
 
-          KD(password_hash, [ auth.nonce, auth.nc, auth.cnonce, QOP, H(A2(auth)) ] * ':')
+          KD password_hash, "#{auth.nonce}:#{auth.nc}:#{auth.cnonce}:#{QOP}:#{H A2(auth)}"
         end
 
       end
diff --git a/lib/rack/auth/digest/nonce.rb b/lib/rack/auth/digest/nonce.rb
index 57089cb3..6c1f28a3 100644
--- a/lib/rack/auth/digest/nonce.rb
+++ b/lib/rack/auth/digest/nonce.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'digest/md5'
 
 module Rack
diff --git a/lib/rack/auth/digest/params.rb b/lib/rack/auth/digest/params.rb
index 2b226e62..f611b3c3 100644
--- a/lib/rack/auth/digest/params.rb
+++ b/lib/rack/auth/digest/params.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Rack
   module Auth
     module Digest
@@ -38,12 +40,12 @@ module Rack
 
         def to_s
           map do |k, v|
-            "#{k}=" << (UNQUOTED.include?(k) ? v.to_s : quote(v))
+            "#{k}=#{(UNQUOTED.include?(k) ? v.to_s : quote(v))}"
           end.join(', ')
         end
 
         def quote(str) # From WEBrick::HTTPUtils
-          '"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
+          '"' + str.gsub(/[\\\"]/o, "\\\1") + '"'
         end
 
       end
diff --git a/lib/rack/auth/digest/request.rb b/lib/rack/auth/digest/request.rb
index 105c7674..a3ab4743 100644
--- a/lib/rack/auth/digest/request.rb
+++ b/lib/rack/auth/digest/request.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/auth/abstract/request'
 require 'rack/auth/digest/params'
 require 'rack/auth/digest/nonce'
diff --git a/lib/rack/body_proxy.rb b/lib/rack/body_proxy.rb
index 7fcfe316..15e4a84f 100644
--- a/lib/rack/body_proxy.rb
+++ b/lib/rack/body_proxy.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Rack
   class BodyProxy
     def initialize(body, &block)
@@ -6,7 +8,7 @@ module Rack
       @closed = false
     end
 
-    def respond_to?(method_name, include_all=false)
+    def respond_to?(method_name, include_all = false)
       case method_name
       when :to_ary, 'to_ary'
         return false
diff --git a/lib/rack/builder.rb b/lib/rack/builder.rb
index 975cf1e1..dcd40c76 100644
--- a/lib/rack/builder.rb
+++ b/lib/rack/builder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Rack
   # Rack::Builder implements a small DSL to iteratively construct Rack
   # applications.
@@ -29,29 +31,43 @@ module Rack
   # You can use +map+ to construct a Rack::URLMap in a convenient way.
 
   class Builder
+
+    # https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom
+    UTF_8_BOM = '\xef\xbb\xbf'
+
     def self.parse_file(config, opts = Server::Options.new)
-      options = {}
-      if config =~ /\.ru$/
-        cfgfile = ::File.read(config)
-        if cfgfile[/^#\\(.*)/] && opts
-          options = opts.parse! $1.split(/\s+/)
-        end
-        cfgfile.sub!(/^__END__\n.*\Z/m, '')
-        app = new_from_string cfgfile, config
+      if config.end_with?('.ru')
+        return self.load_file(config, opts)
       else
         require config
         app = Object.const_get(::File.basename(config, '.rb').split('_').map(&:capitalize).join(''))
+        return app, {}
+      end
+    end
+
+    def self.load_file(path, opts = Server::Options.new)
+      options = {}
+
+      cfgfile = ::File.read(path)
+      cfgfile.slice!(/\A#{UTF_8_BOM}/) if cfgfile.encoding == Encoding::UTF_8
+
+      if cfgfile[/^#\\(.*)/] && opts
+        options = opts.parse! $1.split(/\s+/)
       end
+
+      cfgfile.sub!(/^__END__\n.*\Z/m, '')
+      app = new_from_string cfgfile, path
+
       return app, options
     end
 
-    def self.new_from_string(builder_script, file="(rackup)")
+    def self.new_from_string(builder_script, file = "(rackup)")
       eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
         TOPLEVEL_BINDING, file, 0
     end
 
     def initialize(default_app = nil, &block)
-      @use, @map, @run, @warmup = [], nil, default_app, nil
+      @use, @map, @run, @warmup, @freeze_app = [], nil, default_app, nil, false
       instance_eval(&block) if block_given?
     end
 
@@ -81,7 +97,7 @@ module Rack
     def use(middleware, *args, &block)
       if @map
         mapping, @map = @map, nil
-        @use << proc { |app| generate_map app, mapping }
+        @use << proc { |app| generate_map(app, mapping) }
       end
       @use << proc { |app| middleware.new(app, *args, &block) }
     end
@@ -113,7 +129,7 @@ module Rack
     #
     #   use SomeMiddleware
     #   run MyApp
-    def warmup(prc=nil, &block)
+    def warmup(prc = nil, &block)
       @warmup = prc || block
     end
 
@@ -141,10 +157,17 @@ module Rack
       @map[path] = block
     end
 
+    # Freeze the app (set using run) and all middleware instances when building the application
+    # in to_app.
+    def freeze_app
+      @freeze_app = true
+    end
+
     def to_app
       app = @map ? generate_map(@run, @map) : @run
       fail "missing run or map statement" unless app
-      app = @use.reverse.inject(app) { |a,e| e[a] }
+      app.freeze if @freeze_app
+      app = @use.reverse.inject(app) { |a, e| e[a].tap { |x| x.freeze if @freeze_app } }
       @warmup.call(app) if @warmup
       app
     end
@@ -156,8 +179,8 @@ module Rack
     private
 
     def generate_map(default_app, mapping)
-      mapped = default_app ? {'/' => default_app} : {}
-      mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app }
+      mapped = default_app ? { '/' => default_app } : {}
+      mapping.each { |r, b| mapped[r] = self.class.new(default_app, &b).to_app }
       URLMap.new(mapped)
     end
   end
diff --git a/lib/rack/cascade.rb b/lib/rack/cascade.rb
index 6b8f415a..76bc9a1a 100644
--- a/lib/rack/cascade.rb
+++ b/lib/rack/cascade.rb
@@ -1,14 +1,16 @@
+# frozen_string_literal: true
+
 module Rack
   # Rack::Cascade tries a request on several apps, and returns the
   # first response that is not 404 or 405 (or in a list of configurable
   # status codes).
 
   class Cascade
-    NotFound = [404, {CONTENT_TYPE => "text/plain"}, []]
+    NotFound = [404, { CONTENT_TYPE => "text/plain" }, []]
 
     attr_reader :apps
 
-    def initialize(apps, catch=[404, 405])
+    def initialize(apps, catch = [404, 405])
       @apps = []; @has_app = {}
       apps.each { |app| add app }
 
diff --git a/lib/rack/chunked.rb b/lib/rack/chunked.rb
index 4b8f270e..0c8b4ae8 100644
--- a/lib/rack/chunked.rb
+++ b/lib/rack/chunked.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/utils'
 
 module Rack
@@ -10,7 +12,7 @@ module Rack
     # A body wrapper that emits chunked responses
     class Body
       TERM = "\r\n"
-      TAIL = "0#{TERM}#{TERM}"
+      TAIL = "0#{TERM}"
 
       include Rack::Utils
 
@@ -18,21 +20,38 @@ module Rack
         @body = body
       end
 
-      def each
+      def each(&block)
         term = TERM
         @body.each do |chunk|
           size = chunk.bytesize
           next if size == 0
 
-          chunk = chunk.dup.force_encoding(Encoding::BINARY)
+          chunk = chunk.b
           yield [size.to_s(16), term, chunk, term].join
         end
         yield TAIL
+        insert_trailers(&block)
+        yield TERM
       end
 
       def close
         @body.close if @body.respond_to?(:close)
       end
+
+      private
+
+      def insert_trailers(&block)
+      end
+    end
+
+    class TrailerBody < Body
+      private
+
+      def insert_trailers(&block)
+        @body.trailers.each_pair do |k, v|
+          yield "#{k}: #{v}\r\n"
+        end
+      end
     end
 
     def initialize(app)
@@ -55,14 +74,18 @@ module Rack
       headers = HeaderHash.new(headers)
 
       if ! chunkable_version?(env[HTTP_VERSION]) ||
-         STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
+         STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) ||
          headers[CONTENT_LENGTH] ||
          headers[TRANSFER_ENCODING]
         [status, headers, body]
       else
         headers.delete(CONTENT_LENGTH)
         headers[TRANSFER_ENCODING] = 'chunked'
-        [status, headers, Body.new(body)]
+        if headers['Trailer']
+          [status, headers, TrailerBody.new(body)]
+        else
+          [status, headers, Body.new(body)]
+        end
       end
     end
   end
diff --git a/lib/rack/common_logger.rb b/lib/rack/common_logger.rb
index ae410430..692b2070 100644
--- a/lib/rack/common_logger.rb
+++ b/lib/rack/common_logger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/body_proxy'
 
 module Rack
@@ -23,13 +25,13 @@ module Rack
     #   %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
     FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
 
-    def initialize(app, logger=nil)
+    def initialize(app, logger = nil)
       @app = app
       @logger = logger
     end
 
     def call(env)
-      began_at = Time.now
+      began_at = Utils.clock_time
       status, header, body = @app.call(env)
       header = Utils::HeaderHash.new(header)
       body = BodyProxy.new(body) { log(env, status, header, began_at) }
@@ -39,20 +41,19 @@ module Rack
     private
 
     def log(env, status, header, began_at)
-      now = Time.now
       length = extract_content_length(header)
 
       msg = FORMAT % [
         env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
         env["REMOTE_USER"] || "-",
-        now.strftime("%d/%b/%Y:%H:%M:%S %z"),
+        Time.now.strftime("%d/%b/%Y:%H:%M:%S %z"),
         env[REQUEST_METHOD],
         env[PATH_INFO],
         env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
         env[HTTP_VERSION],
         status.to_s[0..3],
         length,
-        now - began_at ]
+        Utils.clock_time - began_at ]
 
       logger = @logger || env[RACK_ERRORS]
       # Standard library logger doesn't support write but it supports << which actually
@@ -65,8 +66,8 @@ module Rack
     end
 
     def extract_content_length(headers)
-      value = headers[CONTENT_LENGTH] or return '-'
-      value.to_s == '0' ? '-' : value
+      value = headers[CONTENT_LENGTH]
+      !value || value.to_s == '0' ? '-' : value
     end
   end
 end
diff --git a/lib/rack/conditional_get.rb b/lib/rack/conditional_get.rb
index 441dd382..bda8daf6 100644
--- a/lib/rack/conditional_get.rb
+++ b/lib/rack/conditional_get.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/utils'
 
 module Rack
@@ -68,7 +70,7 @@ module Rack
       # anything shorter is invalid, this avoids exceptions for common cases
       # most common being the empty string
       if since && since.length >= 16
-        # NOTE: there is no trivial way to write this in a non execption way
+        # NOTE: there is no trivial way to write this in a non exception way
         #   _rfc2822 returns a hash but is not that usable
         Time.rfc2822(since) rescue nil
       else
diff --git a/lib/rack/config.rb b/lib/rack/config.rb
index dc255d27..41f6f7dd 100644
--- a/lib/rack/config.rb
+++ b/lib/rack/config.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Rack
   # Rack::Config modifies the environment using the block given during
   # initialization.
diff --git a/lib/rack/content_length.rb b/lib/rack/content_length.rb
index 2df7dfc8..e37fc305 100644
--- a/lib/rack/content_length.rb
+++ b/lib/rack/content_length.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/utils'
 require 'rack/body_proxy'
 
@@ -15,7 +17,7 @@ module Rack
       status, headers, body = @app.call(env)
       headers = HeaderHash.new(headers)
 
-      if !STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i) &&
+      if !STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&
          !headers[CONTENT_LENGTH] &&
          !headers[TRANSFER_ENCODING] &&
          body.respond_to?(:to_ary)
diff --git a/lib/rack/content_type.rb b/lib/rack/content_type.rb
index 78ba43b7..010cc37b 100644
--- a/lib/rack/content_type.rb
+++ b/lib/rack/content_type.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/utils'
 
 module Rack
@@ -19,7 +21,7 @@ module Rack
       status, headers, body = @app.call(env)
       headers = Utils::HeaderHash.new(headers)
 
-      unless STATUS_WITH_NO_ENTITY_BODY.include?(status)
+      unless STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i)
         headers[CONTENT_TYPE] ||= @content_type
       end
 
diff --git a/lib/rack/core_ext/regexp.rb b/lib/rack/core_ext/regexp.rb
new file mode 100644
index 00000000..02975345
--- /dev/null
+++ b/lib/rack/core_ext/regexp.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+# Regexp has `match?` since Ruby 2.4
+# so to support Ruby < 2.4 we need to define this method
+
+module Rack
+  module RegexpExtensions
+    if Gem::Version.new(RUBY_VERSION) < Gem::Version.new(2.4)
+      refine Regexp do
+        def match?(string, pos = 0)
+          !!match(string, pos)
+        end unless //.respond_to?(:match?)
+      end
+    end
+  end
+end
diff --git a/lib/rack/deflater.rb b/lib/rack/deflater.rb
index 62a11243..939832ce 100644
--- a/lib/rack/deflater.rb
+++ b/lib/rack/deflater.rb
@@ -1,14 +1,17 @@
+# frozen_string_literal: true
+
 require "zlib"
 require "time"  # for Time.httpdate
 require 'rack/utils'
 
+require_relative 'core_ext/regexp'
+
 module Rack
   # This middleware enables compression of http responses.
   #
   # Currently supported compression algorithms:
   #
   #   * gzip
-  #   * deflate
   #   * identity (no transformation)
   #
   # The middleware automatically detects when compression is supported
@@ -16,19 +19,26 @@ module Rack
   # directive of 'no-transform' is present, or when the response status
   # code is one that doesn't allow an entity body.
   class Deflater
+    using ::Rack::RegexpExtensions
+
     ##
     # Creates Rack::Deflater middleware.
     #
     # [app] rack app instance
     # [options] hash of deflater options, i.e.
     #           'if' - a lambda enabling / disabling deflation based on returned boolean value
-    #                  e.g use Rack::Deflater, :if => lambda { |env, status, headers, body| body.length > 512 }
+    #                  e.g use Rack::Deflater, :if => lambda { |*, body| sum=0; body.each { |i| sum += i.length }; sum > 512 }
     #           'include' - a list of content types that should be compressed
+    #           'sync' - determines if the stream is going to be flused after every chunk.
+    #                    Flushing after every chunk reduces latency for
+    #                    time-sensitive streaming applications, but hurts
+    #                    compression and throughput. Defaults to `true'.
     def initialize(app, options = {})
       @app = app
 
       @condition = options[:if]
       @compressible_types = options[:include]
+      @sync = options[:sync] == false ? false : true
     end
 
     def call(env)
@@ -41,7 +51,7 @@ module Rack
 
       request = Request.new(env)
 
-      encoding = Utils.select_best_encoding(%w(gzip deflate identity),
+      encoding = Utils.select_best_encoding(%w(gzip identity),
                                             request.accept_encoding)
 
       # Set the Vary HTTP header.
@@ -53,37 +63,34 @@ module Rack
       case encoding
       when "gzip"
         headers['Content-Encoding'] = "gzip"
-        headers.delete(CONTENT_LENGTH)
-        mtime = headers.key?("Last-Modified") ?
-          Time.httpdate(headers["Last-Modified"]) : Time.now
-        [status, headers, GzipStream.new(body, mtime)]
-      when "deflate"
-        headers['Content-Encoding'] = "deflate"
-        headers.delete(CONTENT_LENGTH)
-        [status, headers, DeflateStream.new(body)]
+        headers.delete('Content-Length')
+        mtime = headers["Last-Modified"]
+        mtime = Time.httpdate(mtime).to_i if mtime
+        [status, headers, GzipStream.new(body, mtime, @sync)]
       when "identity"
         [status, headers, body]
       when nil
         message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
         bp = Rack::BodyProxy.new([message]) { body.close if body.respond_to?(:close) }
-        [406, {CONTENT_TYPE => "text/plain", CONTENT_LENGTH => message.length.to_s}, bp]
+        [406, { 'Content-Type' => "text/plain", 'Content-Length' => message.length.to_s }, bp]
       end
     end
 
     class GzipStream
-      def initialize(body, mtime)
+      def initialize(body, mtime, sync)
+        @sync = sync
         @body = body
         @mtime = mtime
-        @closed = false
       end
 
       def each(&block)
         @writer = block
-        gzip  =::Zlib::GzipWriter.new(self)
-        gzip.mtime = @mtime
+        gzip = ::Zlib::GzipWriter.new(self)
+        gzip.mtime = @mtime if @mtime
         @body.each { |part|
-          gzip.write(part)
-          gzip.flush
+          len = gzip.write(part)
+          # Flushing empty parts would raise Zlib::BufError.
+          gzip.flush if @sync && len > 0
         }
       ensure
         gzip.close
@@ -95,39 +102,8 @@ module Rack
       end
 
       def close
-        return if @closed
-        @closed = true
-        @body.close if @body.respond_to?(:close)
-      end
-    end
-
-    class DeflateStream
-      DEFLATE_ARGS = [
-        Zlib::DEFAULT_COMPRESSION,
-        # drop the zlib header which causes both Safari and IE to choke
-        -Zlib::MAX_WBITS,
-        Zlib::DEF_MEM_LEVEL,
-        Zlib::DEFAULT_STRATEGY
-      ]
-
-      def initialize(body)
-        @body = body
-        @closed = false
-      end
-
-      def each
-        deflator = ::Zlib::Deflate.new(*DEFLATE_ARGS)
-        @body.each { |part| yield deflator.deflate(part, Zlib::SYNC_FLUSH) }
-        yield fin = deflator.finish
-      ensure
-        deflator.finish unless fin
-        deflator.close
-      end
-
-      def close
-        return if @closed
-        @closed = true
         @body.close if @body.respond_to?(:close)
+        @body = nil
       end
     end
 
@@ -136,14 +112,14 @@ module Rack
     def should_deflate?(env, status, headers, body)
       # Skip compressing empty entity body responses and responses with
       # no-transform set.
-      if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
-          headers[CACHE_CONTROL].to_s =~ /\bno-transform\b/ ||
+      if Utils::STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) ||
+          /\bno-transform\b/.match?(headers['Cache-Control'].to_s) ||
          (headers['Content-Encoding'] && headers['Content-Encoding'] !~ /\bidentity\b/)
         return false
       end
 
       # Skip if @compressible_types are given and does not include request's content type
-      return false if @compressible_types && !(headers.has_key?(CONTENT_TYPE) && @compressible_types.include?(headers[CONTENT_TYPE][/[^;]*/]))
+      return false if @compressible_types && !(headers.has_key?('Content-Type') && @compressible_types.include?(headers['Content-Type'][/[^;]*/]))
 
       # Skip if @condition lambda is given and evaluates to false
       return false if @condition && !@condition.call(env, status, headers, body)
diff --git a/lib/rack/directory.rb b/lib/rack/directory.rb
index 89cfe807..f0acc40d 100644
--- a/lib/rack/directory.rb
+++ b/lib/rack/directory.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'time'
 require 'rack/utils'
 require 'rack/mime'
@@ -41,9 +43,9 @@ table { width:100%%; }
 
     class DirectoryBody < Struct.new(:root, :path, :files)
       def each
-        show_path = Rack::Utils.escape_html(path.sub(/^#{root}/,''))
-        listings = files.map{|f| DIR_FILE % DIR_FILE_escape(*f) }*"\n"
-        page  = DIR_PAGE % [ show_path, show_path , listings ]
+        show_path = Rack::Utils.escape_html(path.sub(/^#{root}/, ''))
+        listings = files.map{|f| DIR_FILE % DIR_FILE_escape(*f) } * "\n"
+        page = DIR_PAGE % [ show_path, show_path, listings ]
         page.each_line{|l| yield l }
       end
 
@@ -56,7 +58,7 @@ table { width:100%%; }
 
     attr_reader :root, :path
 
-    def initialize(root, app=nil)
+    def initialize(root, app = nil)
       @root = ::File.expand_path(root)
       @app = app || Rack::File.new(@root)
       @head = Rack::Head.new(lambda { |env| get env })
@@ -86,9 +88,9 @@ table { width:100%%; }
 
       body = "Bad Request\n"
       size = body.bytesize
-      return [400, {CONTENT_TYPE => "text/plain",
+      return [400, { CONTENT_TYPE => "text/plain",
         CONTENT_LENGTH => size.to_s,
-        "X-Cascade" => "pass"}, [body]]
+        "X-Cascade" => "pass" }, [body]]
     end
 
     def check_forbidden(path_info)
@@ -96,13 +98,13 @@ table { width:100%%; }
 
       body = "Forbidden\n"
       size = body.bytesize
-      return [403, {CONTENT_TYPE => "text/plain",
+      return [403, { CONTENT_TYPE => "text/plain",
         CONTENT_LENGTH => size.to_s,
-        "X-Cascade" => "pass"}, [body]]
+        "X-Cascade" => "pass" }, [body]]
     end
 
     def list_directory(path_info, path, script_name)
-      files = [['../','Parent Directory','','','']]
+      files = [['../', 'Parent Directory', '', '', '']]
       glob = ::File.join(path, '*')
 
       url_head = (script_name.split('/') + path_info.split('/')).map do |part|
@@ -126,7 +128,7 @@ table { width:100%%; }
         files << [ url, basename, size, type, mtime ]
       end
 
-      return [ 200, { CONTENT_TYPE =>'text/html; charset=utf-8'}, DirectoryBody.new(@root, path, files) ]
+      return [ 200, { CONTENT_TYPE => 'text/html; charset=utf-8' }, DirectoryBody.new(@root, path, files) ]
     end
 
     def stat(node)
@@ -154,9 +156,9 @@ table { width:100%%; }
     def entity_not_found(path_info)
       body = "Entity not found: #{path_info}\n"
       size = body.bytesize
-      return [404, {CONTENT_TYPE => "text/plain",
+      return [404, { CONTENT_TYPE => "text/plain",
         CONTENT_LENGTH => size.to_s,
-        "X-Cascade" => "pass"}, [body]]
+        "X-Cascade" => "pass" }, [body]]
     end
 
     # Stolen from Ramaze
diff --git a/lib/rack/etag.rb b/lib/rack/etag.rb
index a0041062..fd3de554 100644
--- a/lib/rack/etag.rb
+++ b/lib/rack/etag.rb
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
+
 require 'rack'
-require 'digest/md5'
+require 'digest/sha2'
 
 module Rack
   # Automatically sets the ETag header on all String bodies.
@@ -13,7 +15,7 @@ module Rack
   # defaults to nil, while the second defaults to "max-age=0, private, must-revalidate"
   class ETag
     ETAG_STRING = Rack::ETAG
-    DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
+    DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
 
     def initialize(app, no_cache_control = nil, cache_control = DEFAULT_CACHE_CONTROL)
       @app = app
diff --git a/lib/rack/events.rb b/lib/rack/events.rb
index 3782a22e..77b71675 100644
--- a/lib/rack/events.rb
+++ b/lib/rack/events.rb
@@ -1,11 +1,13 @@
+# frozen_string_literal: true
+
 require 'rack/response'
 require 'rack/body_proxy'
 
 module Rack
   ### This middleware provides hooks to certain places in the request /
-  #response lifecycle.  This is so that middleware that don't need to filter
-  #the response data can safely leave it alone and not have to send messages
-  #down the traditional "rack stack".
+  # response lifecycle.  This is so that middleware that don't need to filter
+  # the response data can safely leave it alone and not have to send messages
+  # down the traditional "rack stack".
   #
   # The events are:
   #
diff --git a/lib/rack/file.rb b/lib/rack/file.rb
index 0a257b3d..425c1d38 100644
--- a/lib/rack/file.rb
+++ b/lib/rack/file.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'time'
 require 'rack/utils'
 require 'rack/mime'
@@ -19,8 +21,8 @@ module Rack
 
     attr_reader :root
 
-    def initialize(root, headers={}, default_mime = 'text/plain')
-      @root = root
+    def initialize(root, headers = {}, default_mime = 'text/plain')
+      @root = ::File.expand_path root
       @headers = headers
       @default_mime = default_mime
       @head = Rack::Head.new(lambda { |env| get env })
@@ -34,7 +36,7 @@ module Rack
     def get(env)
       request = Rack::Request.new env
       unless ALLOWED_VERBS.include? request.request_method
-        return fail(405, "Method Not Allowed", {'Allow' => ALLOW_HEADER})
+        return fail(405, "Method Not Allowed", { 'Allow' => ALLOW_HEADER })
       end
 
       path_info = Utils.unescape_path request.path_info
@@ -58,7 +60,7 @@ module Rack
 
     def serving(request, path)
       if request.options?
-        return [200, {'Allow' => ALLOW_HEADER, CONTENT_LENGTH => '0'}, []]
+        return [200, { 'Allow' => ALLOW_HEADER, CONTENT_LENGTH => '0' }, []]
       end
       last_modified = ::File.mtime(path).httpdate
       return [304, {}, []] if request.get_header('HTTP_IF_MODIFIED_SINCE') == last_modified
@@ -80,7 +82,7 @@ module Rack
         # No ranges, or multiple ranges (which we don't support):
         # TODO: Support multiple byte-ranges
         response[0] = 200
-        range = 0..size-1
+        range = 0..size - 1
       elsif ranges.empty?
         # Unsatisfiable. Return error, and file size:
         response = fail(416, "Byte range unsatisfiable")
@@ -113,7 +115,7 @@ module Rack
       def each
         ::File.open(path, "rb") do |file|
           file.seek(range.begin)
-          remaining_len = range.end-range.begin+1
+          remaining_len = range.end - range.begin + 1
           while remaining_len > 0
             part = file.read([8192, remaining_len].min)
             break unless part
@@ -158,7 +160,7 @@ module Rack
 
     def filesize path
       # If response_body is present, use its size.
-      return Rack::Utils.bytesize(response_body) if response_body
+      return response_body.bytesize if response_body
 
       #   We check via File::size? whether this file provides size info
       #   via stat (e.g. /proc files often don't), otherwise we have to
diff --git a/lib/rack/handler.rb b/lib/rack/handler.rb
index 70a77fa9..bc0a3bf8 100644
--- a/lib/rack/handler.rb
+++ b/lib/rack/handler.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Rack
   # *Handlers* connect web servers with Rack.
   #
@@ -52,7 +54,7 @@ module Rack
       elsif ENV.include?("RACK_HANDLER")
         self.get(ENV["RACK_HANDLER"])
       else
-        pick ['puma', 'thin', 'webrick']
+        pick ['puma', 'thin', 'falcon', 'webrick']
       end
     end
 
diff --git a/lib/rack/handler/cgi.rb b/lib/rack/handler/cgi.rb
index 52807694..a223c545 100644
--- a/lib/rack/handler/cgi.rb
+++ b/lib/rack/handler/cgi.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
 require 'rack/content_length'
 require 'rack/rewindable_input'
 
 module Rack
   module Handler
     class CGI
-      def self.run(app, options=nil)
+      def self.run(app, options = nil)
         $stdin.binmode
         serve app
       end
diff --git a/lib/rack/handler/fastcgi.rb b/lib/rack/handler/fastcgi.rb
index e918dc94..b3f825da 100644
--- a/lib/rack/handler/fastcgi.rb
+++ b/lib/rack/handler/fastcgi.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'fcgi'
 require 'socket'
 require 'rack/content_length'
@@ -7,7 +9,7 @@ if defined? FCGI::Stream
   class FCGI::Stream
     alias _rack_read_without_buffer read
 
-    def read(n, buffer=nil)
+    def read(n, buffer = nil)
       buf = _rack_read_without_buffer n
       buffer.replace(buf.to_s)  if buffer
       buf
@@ -18,7 +20,7 @@ end
 module Rack
   module Handler
     class FastCGI
-      def self.run(app, options={})
+      def self.run(app, options = {})
         if options[:File]
           STDIN.reopen(UNIXServer.new(options[:File]))
         elsif options[:Port]
diff --git a/lib/rack/handler/lsws.rb b/lib/rack/handler/lsws.rb
index d2cfd793..803182a2 100644
--- a/lib/rack/handler/lsws.rb
+++ b/lib/rack/handler/lsws.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'lsapi'
 require 'rack/content_length'
 require 'rack/rewindable_input'
@@ -5,7 +7,7 @@ require 'rack/rewindable_input'
 module Rack
   module Handler
     class LSWS
-      def self.run(app, options=nil)
+      def self.run(app, options = nil)
         while LSAPI.accept != nil
           serve app
         end
diff --git a/lib/rack/handler/scgi.rb b/lib/rack/handler/scgi.rb
index e056a01d..c8e91606 100644
--- a/lib/rack/handler/scgi.rb
+++ b/lib/rack/handler/scgi.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'scgi'
 require 'stringio'
 require 'rack/content_length'
@@ -8,12 +10,12 @@ module Rack
     class SCGI < ::SCGI::Processor
       attr_accessor :app
 
-      def self.run(app, options=nil)
+      def self.run(app, options = nil)
         options[:Socket] = UNIXServer.new(options[:File]) if options[:File]
-        new(options.merge(:app=>app,
-                          :host=>options[:Host],
-                          :port=>options[:Port],
-                          :socket=>options[:Socket])).listen
+        new(options.merge(app: app,
+                          host: options[:Host],
+                          port: options[:Port],
+                          socket: options[:Socket])).listen
       end
 
       def self.valid_options
@@ -41,7 +43,8 @@ module Rack
         env[QUERY_STRING] ||= ""
         env[SCRIPT_NAME] = ""
 
-        rack_input = StringIO.new(input_body, encoding: Encoding::BINARY)
+        rack_input = StringIO.new(input_body)
+        rack_input.set_encoding(Encoding::BINARY)
 
         env.update(
           RACK_VERSION      => Rack::VERSION,
diff --git a/lib/rack/handler/thin.rb b/lib/rack/handler/thin.rb
index ca880646..100dfd11 100644
--- a/lib/rack/handler/thin.rb
+++ b/lib/rack/handler/thin.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require "thin"
 require "thin/server"
 require "thin/logging"
@@ -8,7 +10,7 @@ require "rack/chunked"
 module Rack
   module Handler
     class Thin
-      def self.run(app, options={})
+      def self.run(app, options = {})
         environment  = ENV['RACK_ENV'] || 'development'
         default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
 
diff --git a/lib/rack/handler/webrick.rb b/lib/rack/handler/webrick.rb
index 95aa8927..4affdbde 100644
--- a/lib/rack/handler/webrick.rb
+++ b/lib/rack/handler/webrick.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'webrick'
 require 'stringio'
 require 'rack/content_length'
@@ -22,7 +24,7 @@ end
 module Rack
   module Handler
     class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
-      def self.run(app, options={})
+      def self.run(app, options = {})
         environment  = ENV['RACK_ENV'] || 'development'
         default_host = environment == 'development' ? 'localhost' : nil
 
@@ -79,17 +81,18 @@ module Rack
         env[QUERY_STRING] ||= ""
         unless env[PATH_INFO] == ""
           path, n = req.request_uri.path, env[SCRIPT_NAME].length
-          env[PATH_INFO] = path[n, path.length-n]
+          env[PATH_INFO] = path[n, path.length - n]
         end
         env[REQUEST_PATH] ||= [env[SCRIPT_NAME], env[PATH_INFO]].join
 
         status, headers, body = @app.call(env)
         begin
           res.status = status.to_i
+          io_lambda = nil
           headers.each { |k, vs|
-            next if k.downcase == RACK_HIJACK
-
-            if k.downcase == "set-cookie"
+            if k == RACK_HIJACK
+              io_lambda = vs
+            elsif k.downcase == "set-cookie"
               res.cookies.concat vs.split("\n")
             else
               # Since WEBrick won't accept repeated headers,
@@ -98,7 +101,6 @@ module Rack
             end
           }
 
-          io_lambda = headers[RACK_HIJACK]
           if io_lambda
             rd, wr = IO.pipe
             res.body = rd
diff --git a/lib/rack/head.rb b/lib/rack/head.rb
index 6f1d7472..c257ae4d 100644
--- a/lib/rack/head.rb
+++ b/lib/rack/head.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/body_proxy'
 
 module Rack
diff --git a/lib/rack/lint.rb b/lib/rack/lint.rb
index 54d37822..17b188f4 100644
--- a/lib/rack/lint.rb
+++ b/lib/rack/lint.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/utils'
 require 'forwardable'
 
@@ -33,7 +35,7 @@ module Rack
 
     ## A Rack application is a Ruby object (not a class) that
     ## responds to +call+.
-    def call(env=nil)
+    def call(env = nil)
       dup._call(env)
     end
 
@@ -123,9 +125,8 @@ module Rack
       ##                            the presence or absence of the
       ##                            appropriate HTTP header in the
       ##                            request. See
-      ##                            <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
-      ##                            RFC3875 section 4.1.18</a> for
-      ##                            specific behavior.
+      ##                            {RFC3875 section 4.1.18}[https://tools.ietf.org/html/rfc3875#section-4.1.18]
+      ##                            for specific behavior.
 
       ## In addition to this, the Rack environment must include these
       ## Rack-specific variables:
@@ -263,7 +264,7 @@ module Rack
       ## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
       ## (use the versions without <tt>HTTP_</tt>).
       %w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
-        assert("env contains #{header}, must use #{header[5,-1]}") {
+        assert("env contains #{header}, must use #{header[5, -1]}") {
           not env.include? header
         }
       }
@@ -659,10 +660,10 @@ module Rack
     def check_content_type(status, headers)
       headers.each { |key, value|
         ## There must not be a <tt>Content-Type</tt>, when the +Status+ is 1xx,
-        ## 204, 205 or 304.
+        ## 204 or 304.
         if key.downcase == "content-type"
           assert("Content-Type header found in #{status} response, not allowed") {
-            not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
+            not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
           }
           return
         end
@@ -674,9 +675,9 @@ module Rack
       headers.each { |key, value|
         if key.downcase == 'content-length'
           ## There must not be a <tt>Content-Length</tt> header when the
-          ## +Status+ is 1xx, 204, 205 or 304.
+          ## +Status+ is 1xx, 204 or 304.
           assert("Content-Length header found in #{status} response, not allowed") {
-            not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
+            not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
           }
           @content_length = value
         end
diff --git a/lib/rack/lobster.rb b/lib/rack/lobster.rb
index 4d6e39f2..77b607c3 100644
--- a/lib/rack/lobster.rb
+++ b/lib/rack/lobster.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'zlib'
 
 require 'rack/request'
@@ -25,8 +27,8 @@ module Rack
       content = ["<title>Lobstericious!</title>",
                  "<pre>", lobster, "</pre>",
                  "<a href='#{href}'>flip!</a>"]
-      length = content.inject(0) { |a,e| a+e.size }.to_s
-      [200, {CONTENT_TYPE => "text/html", CONTENT_LENGTH => length}, content]
+      length = content.inject(0) { |a, e| a + e.size }.to_s
+      [200, { CONTENT_TYPE => "text/html", CONTENT_LENGTH => length }, content]
     }
 
     def call(env)
@@ -37,8 +39,8 @@ module Rack
             gsub('\\', 'TEMP').
             gsub('/', '\\').
             gsub('TEMP', '/').
-            gsub('{','}').
-            gsub('(',')')
+            gsub('{', '}').
+            gsub('(', ')')
         end.join("\n")
         href = "?flip=right"
       elsif req.GET["flip"] == "crash"
@@ -65,6 +67,6 @@ if $0 == __FILE__
   require 'rack'
   require 'rack/show_exceptions'
   Rack::Server.start(
-    :app => Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), :Port => 9292
+    app: Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), Port: 9292
   )
 end
diff --git a/lib/rack/lock.rb b/lib/rack/lock.rb
index 923dca59..96366cd3 100644
--- a/lib/rack/lock.rb
+++ b/lib/rack/lock.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'thread'
 require 'rack/body_proxy'
 
@@ -11,12 +13,21 @@ module Rack
 
     def call(env)
       @mutex.lock
+      @env = env
+      @old_rack_multithread = env[RACK_MULTITHREAD]
       begin
-        response = @app.call(env.merge(RACK_MULTITHREAD => false))
-        returned = response << BodyProxy.new(response.pop) { @mutex.unlock }
+        response = @app.call(env.merge!(RACK_MULTITHREAD => false))
+        returned = response << BodyProxy.new(response.pop) { unlock }
       ensure
-        @mutex.unlock unless returned
+        unlock unless returned
       end
     end
+
+    private
+
+    def unlock
+      @mutex.unlock
+      @env[RACK_MULTITHREAD] = @old_rack_multithread
+    end
   end
 end
diff --git a/lib/rack/logger.rb b/lib/rack/logger.rb
index 01fc321c..6c4bede0 100644
--- a/lib/rack/logger.rb
+++ b/lib/rack/logger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'logger'
 
 module Rack
diff --git a/lib/rack/media_type.rb b/lib/rack/media_type.rb
index f9397453..41937c99 100644
--- a/lib/rack/media_type.rb
+++ b/lib/rack/media_type.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Rack
   # Rack::MediaType parse media type and parameters out of content_type string
 
@@ -13,7 +15,7 @@ module Rack
       # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
       def type(content_type)
         return nil unless content_type
-        content_type.split(SPLIT_PATTERN, 2).first.downcase
+        content_type.split(SPLIT_PATTERN, 2).first.tap &:downcase!
       end
 
       # The media type parameters provided in CONTENT_TYPE as a Hash, or
@@ -23,9 +25,12 @@ module Rack
       #   { 'charset' => 'utf-8' }
       def params(content_type)
         return {} if content_type.nil?
-        Hash[*content_type.split(SPLIT_PATTERN)[1..-1].
-          collect { |s| s.split('=', 2) }.
-          map { |k,v| [k.downcase, strip_doublequotes(v)] }.flatten]
+
+        content_type.split(SPLIT_PATTERN)[1..-1].each_with_object({}) do |s, hsh|
+          k, v = s.split('=', 2)
+
+          hsh[k.tap(&:downcase!)] = strip_doublequotes(v)
+        end
       end
 
       private
diff --git a/lib/rack/method_override.rb b/lib/rack/method_override.rb
index f5637771..453901fc 100644
--- a/lib/rack/method_override.rb
+++ b/lib/rack/method_override.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
 module Rack
   class MethodOverride
     HTTP_METHODS = %w[GET HEAD PUT POST DELETE OPTIONS PATCH LINK UNLINK]
 
-    METHOD_OVERRIDE_PARAM_KEY = "_method".freeze
-    HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE".freeze
+    METHOD_OVERRIDE_PARAM_KEY = "_method"
+    HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE"
     ALLOWED_METHODS = %w[POST]
 
     def initialize(app)
@@ -26,7 +28,11 @@ module Rack
       req = Request.new(env)
       method = method_override_param(req) ||
         env[HTTP_METHOD_OVERRIDE_HEADER]
-      method.to_s.upcase
+      begin
+        method.to_s.upcase
+      rescue ArgumentError
+        env[RACK_ERRORS].puts "Invalid string for method"
+      end
     end
 
     private
@@ -38,6 +44,9 @@ module Rack
     def method_override_param(req)
       req.POST[METHOD_OVERRIDE_PARAM_KEY]
     rescue Utils::InvalidParameterError, Utils::ParameterTypeError
+      req.get_header(RACK_ERRORS).puts "Invalid or incomplete POST params"
+    rescue EOFError
+      req.get_header(RACK_ERRORS).puts "Bad request content body"
     end
   end
 end
diff --git a/lib/rack/mime.rb b/lib/rack/mime.rb
index d82dc131..f6c02c1f 100644
--- a/lib/rack/mime.rb
+++ b/lib/rack/mime.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Rack
   module Mime
     # Returns String with mime type if found, otherwise use +fallback+.
@@ -13,7 +15,7 @@ module Rack
     # This is a shortcut for:
     #     Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream')
 
-    def mime_type(ext, fallback='application/octet-stream')
+    def mime_type(ext, fallback = 'application/octet-stream')
       MIME_TYPES.fetch(ext.to_s.downcase, fallback)
     end
     module_function :mime_type
@@ -306,6 +308,7 @@ module Rack
       ".lvp"       => "audio/vnd.lucent.voice",
       ".lwp"       => "application/vnd.lotus-wordpro",
       ".m3u"       => "audio/x-mpegurl",
+      ".m3u8"      => "application/x-mpegurl",
       ".m4a"       => "audio/mp4a-latm",
       ".m4v"       => "video/mp4",
       ".ma"        => "application/mathematica",
@@ -343,6 +346,7 @@ module Rack
       ".mp4s"      => "application/mp4",
       ".mp4v"      => "video/mp4",
       ".mpc"       => "application/vnd.mophun.certificate",
+      ".mpd"       => "application/dash+xml",
       ".mpeg"      => "video/mpeg",
       ".mpg"       => "video/mpeg",
       ".mpga"      => "audio/mpeg",
@@ -542,6 +546,7 @@ module Rack
       ".spp"       => "application/scvp-vp-response",
       ".spq"       => "application/scvp-vp-request",
       ".src"       => "application/x-wais-source",
+      ".srt"       => "text/srt",
       ".srx"       => "application/sparql-results+xml",
       ".sse"       => "application/vnd.kodak-descriptor",
       ".ssf"       => "application/vnd.epson.ssf",
@@ -576,6 +581,7 @@ module Rack
       ".tr"        => "text/troff",
       ".tra"       => "application/vnd.trueapp",
       ".trm"       => "application/x-msterminal",
+      ".ts"        => "video/mp2t",
       ".tsv"       => "text/tab-separated-values",
       ".ttf"       => "application/octet-stream",
       ".twd"       => "application/vnd.simtech-mindmapper",
@@ -600,9 +606,11 @@ module Rack
       ".vrml"      => "model/vrml",
       ".vsd"       => "application/vnd.visio",
       ".vsf"       => "application/vnd.vsf",
+      ".vtt"       => "text/vtt",
       ".vtu"       => "model/vnd.vtu",
       ".vxml"      => "application/voicexml+xml",
       ".war"       => "application/java-archive",
+      ".wasm"      => "application/wasm",
       ".wav"       => "audio/x-wav",
       ".wax"       => "audio/x-ms-wax",
       ".wbmp"      => "image/vnd.wap.wbmp",
diff --git a/lib/rack/mock.rb b/lib/rack/mock.rb
index 4ebc4df1..f2b94832 100644
--- a/lib/rack/mock.rb
+++ b/lib/rack/mock.rb
@@ -1,9 +1,12 @@
+# frozen_string_literal: true
+
 require 'uri'
 require 'stringio'
 require 'rack'
 require 'rack/lint'
 require 'rack/utils'
 require 'rack/response'
+require 'cgi/cookie'
 
 module Rack
   # Rack::MockRequest helps testing your Rack application without
@@ -53,16 +56,16 @@ module Rack
       @app = app
     end
 
-    def get(uri, opts={})     request(GET, uri, opts)     end
-    def post(uri, opts={})    request(POST, uri, opts)    end
-    def put(uri, opts={})     request(PUT, uri, opts)     end
-    def patch(uri, opts={})   request(PATCH, uri, opts)   end
-    def delete(uri, opts={})  request(DELETE, uri, opts)  end
-    def head(uri, opts={})    request(HEAD, uri, opts)    end
-    def options(uri, opts={}) request(OPTIONS, uri, opts) end
+    def get(uri, opts = {})     request(GET, uri, opts)     end
+    def post(uri, opts = {})    request(POST, uri, opts)    end
+    def put(uri, opts = {})     request(PUT, uri, opts)     end
+    def patch(uri, opts = {})   request(PATCH, uri, opts)   end
+    def delete(uri, opts = {})  request(DELETE, uri, opts)  end
+    def head(uri, opts = {})    request(HEAD, uri, opts)    end
+    def options(uri, opts = {}) request(OPTIONS, uri, opts) end
 
-    def request(method=GET, uri="", opts={})
-      env = self.class.env_for(uri, opts.merge(:method => method))
+    def request(method = GET, uri = "", opts = {})
+      env = self.class.env_for(uri, opts.merge(method: method))
 
       if opts[:lint]
         app = Rack::Lint.new(@app)
@@ -71,7 +74,7 @@ module Rack
       end
 
       errors = env[RACK_ERRORS]
-      status, headers, body  = app.call(env)
+      status, headers, body = app.call(env)
       MockResponse.new(status, headers, body, errors)
     ensure
       body.close if body.respond_to?(:close)
@@ -85,19 +88,19 @@ module Rack
     end
 
     # Return the Rack environment used for a request to +uri+.
-    def self.env_for(uri="", opts={})
+    def self.env_for(uri = "", opts = {})
       uri = parse_uri_rfc2396(uri)
       uri.path = "/#{uri.path}" unless uri.path[0] == ?/
 
       env = DEFAULT_ENV.dup
 
-      env[REQUEST_METHOD]  = opts[:method] ? opts[:method].to_s.upcase : GET
-      env[SERVER_NAME]     = uri.host || "example.org"
-      env[SERVER_PORT]     = uri.port ? uri.port.to_s : "80"
-      env[QUERY_STRING]    = uri.query.to_s
-      env[PATH_INFO]       = (!uri.path || uri.path.empty?) ? "/" : uri.path
-      env[RACK_URL_SCHEME] = uri.scheme || "http"
-      env[HTTPS]           = env[RACK_URL_SCHEME] == "https" ? "on" : "off"
+      env[REQUEST_METHOD]  = (opts[:method] ? opts[:method].to_s.upcase : GET).b
+      env[SERVER_NAME]     = (uri.host || "example.org").b
+      env[SERVER_PORT]     = (uri.port ? uri.port.to_s : "80").b
+      env[QUERY_STRING]    = (uri.query.to_s).b
+      env[PATH_INFO]       = ((!uri.path || uri.path.empty?) ? "/" : uri.path).b
+      env[RACK_URL_SCHEME] = (uri.scheme || "http").b
+      env[HTTPS]           = (env[RACK_URL_SCHEME] == "https" ? "on" : "off").b
 
       env[SCRIPT_NAME] = opts[:script_name] || ""
 
@@ -128,7 +131,7 @@ module Rack
         end
       end
 
-      empty_str = String.new.force_encoding(Encoding::ASCII_8BIT)
+      empty_str = String.new
       opts[:input] ||= empty_str
       if String === opts[:input]
         rack_input = StringIO.new(opts[:input])
@@ -139,7 +142,7 @@ module Rack
       rack_input.set_encoding(Encoding::BINARY)
       env[RACK_INPUT] = rack_input
 
-      env["CONTENT_LENGTH"] ||= env[RACK_INPUT].length.to_s
+      env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size)
 
       opts.each { |field, value|
         env[field] = value  if String === field
@@ -155,14 +158,15 @@ module Rack
 
   class MockResponse < Rack::Response
     # Headers
-    attr_reader :original_headers
+    attr_reader :original_headers, :cookies
 
     # Errors
     attr_accessor :errors
 
-    def initialize(status, headers, body, errors=StringIO.new(""))
+    def initialize(status, headers, body, errors = StringIO.new(""))
       @original_headers = headers
       @errors           = errors.string if errors.respond_to?(:string)
+      @cookies = parse_cookies_from_header
 
       super(body, status, headers)
     end
@@ -190,7 +194,54 @@ module Rack
     end
 
     def empty?
-      [201, 204, 205, 304].include? status
+      [201, 204, 304].include? status
+    end
+
+    def cookie(name)
+      cookies.fetch(name, nil)
+    end
+
+    private
+
+    def parse_cookies_from_header
+      cookies = Hash.new
+      if original_headers.has_key? 'Set-Cookie'
+        set_cookie_header = original_headers.fetch('Set-Cookie')
+        set_cookie_header.split("\n").each do |cookie|
+          cookie_name, cookie_filling = cookie.split('=', 2)
+          cookie_attributes = identify_cookie_attributes cookie_filling
+          parsed_cookie = CGI::Cookie.new(
+            'name' => cookie_name.strip,
+            'value' => cookie_attributes.fetch('value'),
+            'path' => cookie_attributes.fetch('path', nil),
+            'domain' => cookie_attributes.fetch('domain', nil),
+            'expires' => cookie_attributes.fetch('expires', nil),
+            'secure' => cookie_attributes.fetch('secure', false)
+          )
+          cookies.store(cookie_name, parsed_cookie)
+        end
+      end
+      cookies
     end
+
+    def identify_cookie_attributes(cookie_filling)
+      cookie_bits = cookie_filling.split(';')
+      cookie_attributes = Hash.new
+      cookie_attributes.store('value', cookie_bits[0].strip)
+      cookie_bits.each do |bit|
+        if bit.include? '='
+          cookie_attribute, attribute_value = bit.split('=')
+          cookie_attributes.store(cookie_attribute.strip, attribute_value.strip)
+          if cookie_attribute.include? 'max-age'
+            cookie_attributes.store('expires', Time.now + attribute_value.strip.to_i)
+          end
+        end
+        if bit.include? 'secure'
+          cookie_attributes.store('secure', true)
+        end
+      end
+      cookie_attributes
+    end
+
   end
 end
diff --git a/lib/rack/multipart.rb b/lib/rack/multipart.rb
index db59ee59..31ac29eb 100644
--- a/lib/rack/multipart.rb
+++ b/lib/rack/multipart.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/multipart/parser'
 
 module Rack
diff --git a/lib/rack/multipart/generator.rb b/lib/rack/multipart/generator.rb
index f0b70a8d..9ed2bb07 100644
--- a/lib/rack/multipart/generator.rb
+++ b/lib/rack/multipart/generator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Rack
   module Multipart
     class Generator
@@ -27,21 +29,18 @@ module Rack
 
       private
       def multipart?
-        multipart = false
-
         query = lambda { |value|
           case value
           when Array
-            value.each(&query)
+            value.any?(&query)
           when Hash
-            value.values.each(&query)
+            value.values.any?(&query)
           when Rack::Multipart::UploadedFile
-            multipart = true
+            true
           end
         }
-        @params.values.each(&query)
 
-        multipart
+        @params.values.any?(&query)
       end
 
       def flattened_params
diff --git a/lib/rack/multipart/parser.rb b/lib/rack/multipart/parser.rb
index f661da10..7c38d5f3 100644
--- a/lib/rack/multipart/parser.rb
+++ b/lib/rack/multipart/parser.rb
@@ -1,16 +1,24 @@
+# frozen_string_literal: true
+
 require 'rack/utils'
+require 'strscan'
+require 'rack/core_ext/regexp'
 
 module Rack
   module Multipart
     class MultipartPartLimitError < Errno::EMFILE; end
 
     class Parser
-      BUFSIZE = 16384
+      using ::Rack::RegexpExtensions
+
+      BUFSIZE = 1_048_576
       TEXT_PLAIN = "text/plain"
       TEMPFILE_FACTORY = lambda { |filename, content_type|
-        Tempfile.new(["RackMultipart", ::File.extname(filename)])
+        Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
       }
 
+      BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
+
       class BoundedIO # :nodoc:
         def initialize(io, content_length)
           @io             = io
@@ -18,15 +26,15 @@ module Rack
           @cursor = 0
         end
 
-        def read(size)
+        def read(size, outbuf = nil)
           return if @cursor >= @content_length
 
           left = @content_length - @cursor
 
           str = if left < size
-                  @io.read left
+                  @io.read left, outbuf
                 else
-                  @io.read size
+                  @io.read size, outbuf
                 end
 
           if str
@@ -39,8 +47,6 @@ module Rack
           str
         end
 
-        def eof?; @content_length == @cursor; end
-
         def rewind
           @io.rewind
         end
@@ -63,13 +69,14 @@ module Rack
         return EMPTY unless boundary
 
         io = BoundedIO.new(io, content_length) if content_length
+        outbuf = String.new
 
         parser = new(boundary, tmpfile, bufsize, qp)
-        parser.on_read io.read(bufsize), io.eof?
+        parser.on_read io.read(bufsize, outbuf)
 
         loop do
           break if parser.state == :DONE
-          parser.on_read io.read(bufsize), io.eof?
+          parser.on_read io.read(bufsize, outbuf)
         end
 
         io.rewind
@@ -92,14 +99,14 @@ module Rack
               # those which give the lone filename.
               fn = filename.split(/[\/\\]/).last
 
-              data = {:filename => fn, :type => content_type,
-                      :name => name, :tempfile => body, :head => head}
+              data = { filename: fn, type: content_type,
+                      name: name, tempfile: body, head: head }
             elsif !filename && content_type && body.is_a?(IO)
               body.rewind
 
               # Generic multipart cases, not coming from a form
-              data = {:type => content_type,
-                      :name => name, :tempfile => body, :head => head}
+              data = { type: content_type,
+                      name: name, tempfile: body, head: head }
             end
 
             yield data
@@ -135,11 +142,12 @@ module Rack
             klass = TempfilePart
             @open_files += 1
           else
-            body = ''.force_encoding(Encoding::ASCII_8BIT)
+            body = String.new
             klass = BufferPart
           end
 
           @mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
+
           check_open_files
         end
 
@@ -165,25 +173,26 @@ module Rack
       attr_reader :state
 
       def initialize(boundary, tempfile, bufsize, query_parser)
-        @buf            = "".force_encoding(Encoding::ASCII_8BIT)
-
         @query_parser   = query_parser
         @params         = query_parser.make_params
         @boundary       = "--#{boundary}"
-        @boundary_size  = @boundary.bytesize + EOL.size
         @bufsize        = bufsize
 
-        @rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
         @full_boundary = @boundary
         @end_boundary = @boundary + '--'
         @state = :FAST_FORWARD
         @mime_index = 0
         @collector = Collector.new tempfile
+
+        @sbuf = StringScanner.new("".dup)
+        @body_regex = /(.*?)(#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/m
+        @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
+        @head_regex = /(.*?#{EOL})#{EOL}/m
       end
 
-      def on_read content, eof
-        handle_empty_content!(content, eof)
-        @buf << content
+      def on_read content
+        handle_empty_content!(content)
+        @sbuf.concat content
         run_parser
       end
 
@@ -194,7 +203,6 @@ module Rack
             @query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
           end
         end
-
         MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
       end
 
@@ -221,7 +229,7 @@ module Rack
         if consume_boundary
           @state = :MIME_HEAD
         else
-          raise EOFError, "bad content body" if @buf.bytesize >= @bufsize
+          raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
           :want_read
         end
       end
@@ -229,19 +237,16 @@ module Rack
       def handle_consume_token
         tok = consume_boundary
         # break if we're at the end of a buffer, but not if it is the end of a field
-        if tok == :END_BOUNDARY || (@buf.empty? && tok != :BOUNDARY)
-          @state = :DONE
+        @state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
+          :DONE
         else
-          @state = :MIME_HEAD
+          :MIME_HEAD
         end
       end
 
       def handle_mime_head
-        if @buf.index(EOL + EOL)
-          i = @buf.index(EOL+EOL)
-          head = @buf.slice!(0, i+2) # First \r\n
-          @buf.slice!(0, 2)          # Second \r\n
-
+        if @sbuf.scan_until(@head_regex)
+          head = @sbuf[1]
           content_type = head[MULTIPART_CONTENT_TYPE, 1]
           if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
             name = Rack::Auth::Digest::Params::dequote(name)
@@ -252,7 +257,7 @@ module Rack
           filename = get_filename(head)
 
           if name.nil? || name.empty?
-            name = filename || "#{content_type || TEXT_PLAIN}[]"
+            name = filename || "#{content_type || TEXT_PLAIN}[]".dup
           end
 
           @collector.on_mime_head @mime_index, head, filename, content_type, name
@@ -263,31 +268,33 @@ module Rack
       end
 
       def handle_mime_body
-        if @buf =~ rx
-          # Save the rest.
-          if i = @buf.index(rx)
-            @collector.on_mime_body @mime_index, @buf.slice!(0, i)
-            @buf.slice!(0, 2) # Remove \r\n after the content
-          end
+        if @sbuf.check_until(@body_regex) # check but do not advance the pointer yet
+          body = @sbuf[1]
+          @collector.on_mime_body @mime_index, body
+          @sbuf.pos += body.length + 2 # skip \r\n after the content
           @state = :CONSUME_TOKEN
           @mime_index += 1
         else
+          # Save what we have so far
+          if @rx_max_size < @sbuf.rest_size
+            delta = @sbuf.rest_size - @rx_max_size
+            @collector.on_mime_body @mime_index, @sbuf.peek(delta)
+            @sbuf.pos += delta
+            @sbuf.string = @sbuf.rest
+          end
           :want_read
         end
       end
 
       def full_boundary; @full_boundary; end
 
-      def rx; @rx; end
-
       def consume_boundary
-        while @buf.gsub!(/\A([^\n]*(?:\n|\Z))/, '')
-          read_buffer = $1
+        while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
           case read_buffer.strip
           when full_boundary then return :BOUNDARY
           when @end_boundary then return :END_BOUNDARY
           end
-          return if @buf.empty?
+          return if @sbuf.eos?
         end
       end
 
@@ -308,8 +315,8 @@ module Rack
 
         return unless filename
 
-        if filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
-          filename = Utils.unescape(filename)
+        if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
+          filename = Utils.unescape_path(filename)
         end
 
         filename.scrub!
@@ -325,7 +332,7 @@ module Rack
         filename
       end
 
-      CHARSET   = "charset"
+      CHARSET = "charset"
 
       def tag_multipart_encoding(filename, content_type, name, body)
         name = name.to_s
@@ -342,7 +349,7 @@ module Rack
           if TEXT_PLAIN == type_subtype
             rest         = list.drop 1
             rest.each do |param|
-              k,v = param.split('=', 2)
+              k, v = param.split('=', 2)
               k.strip!
               v.strip!
               v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
@@ -355,11 +362,9 @@ module Rack
         body.force_encoding(encoding)
       end
 
-
-      def handle_empty_content!(content, eof)
+      def handle_empty_content!(content)
         if content.nil? || content.empty?
-          raise EOFError if eof
-          return true
+          raise EOFError
         end
       end
     end
diff --git a/lib/rack/multipart/uploaded_file.rb b/lib/rack/multipart/uploaded_file.rb
index 924b1f08..d01f2d6f 100644
--- a/lib/rack/multipart/uploaded_file.rb
+++ b/lib/rack/multipart/uploaded_file.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Rack
   module Multipart
     class UploadedFile
diff --git a/lib/rack/null_logger.rb b/lib/rack/null_logger.rb
index abc61206..3eff73d6 100644
--- a/lib/rack/null_logger.rb
+++ b/lib/rack/null_logger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Rack
   class NullLogger
     def initialize(app)
diff --git a/lib/rack/query_parser.rb b/lib/rack/query_parser.rb
index 17b77128..fce1ce91 100644
--- a/lib/rack/query_parser.rb
+++ b/lib/rack/query_parser.rb
@@ -1,5 +1,11 @@
+# frozen_string_literal: true
+
+require_relative 'core_ext/regexp'
+
 module Rack
   class QueryParser
+    using ::Rack::RegexpExtensions
+
     DEFAULT_SEP = /[&;] */n
     COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
 
@@ -36,7 +42,7 @@ module Rack
 
       (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
         next if p.empty?
-        k, v = p.split('='.freeze, 2).map!(&unescaper)
+        k, v = p.split('=', 2).map!(&unescaper)
 
         if cur = params[k]
           if cur.class == Array
@@ -61,8 +67,8 @@ module Rack
       return {} if qs.nil? || qs.empty?
       params = make_params
 
-      (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
-        k, v = p.split('='.freeze, 2).map! { |s| unescape(s) }
+      qs.split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
+        k, v = p.split('=', 2).map! { |s| unescape(s) }
 
         normalize_params(params, k, v, param_depth_limit)
       end
@@ -79,22 +85,22 @@ module Rack
       raise RangeError if depth <= 0
 
       name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
-      k = $1 || ''.freeze
-      after = $' || ''.freeze
+      k = $1 || ''
+      after = $' || ''
 
       if k.empty?
-        if !v.nil? && name == "[]".freeze
+        if !v.nil? && name == "[]"
           return Array(v)
         else
           return
         end
       end
 
-      if after == ''.freeze
+      if after == ''
         params[k] = v
-      elsif after == "[".freeze
+      elsif after == "["
         params[name] = v
-      elsif after == "[]".freeze
+      elsif after == "[]"
         params[k] ||= []
         raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
         params[k] << v
@@ -102,8 +108,7 @@ module Rack
         child_key = $1
         params[k] ||= []
         raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
-        first_key = child_key.gsub(/[\[\]]/, ' ').split.first
-        if params_hash_type?(params[k].last) && !params[k].last.key?(first_key)
+        if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
           normalize_params(params[k].last, child_key, v, depth - 1)
         else
           params[k] << normalize_params(make_params, child_key, v, depth - 1)
@@ -135,6 +140,18 @@ module Rack
       obj.kind_of?(@params_class)
     end
 
+    def params_hash_has_key?(hash, key)
+      return false if /\[\]/.match?(key)
+
+      key.split(/[\[\]]+/).inject(hash) do |h, part|
+        next h if part == ''
+        return false unless params_hash_type?(h) && h.key?(part)
+        h[part]
+      end
+
+      true
+    end
+
     def unescape(s)
       Utils.unescape(s)
     end
diff --git a/lib/rack/recursive.rb b/lib/rack/recursive.rb
index 7645d284..6a94ca83 100644
--- a/lib/rack/recursive.rb
+++ b/lib/rack/recursive.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'uri'
 
 module Rack
@@ -10,14 +12,14 @@ module Rack
   class ForwardRequest < Exception
     attr_reader :url, :env
 
-    def initialize(url, env={})
+    def initialize(url, env = {})
       @url = URI(url)
       @env = env
 
-      @env[PATH_INFO] =       @url.path
-      @env[QUERY_STRING] =    @url.query  if @url.query
-      @env[HTTP_HOST] =       @url.host   if @url.host
-      @env["HTTP_PORT"] =     @url.port   if @url.port
+      @env[PATH_INFO]       = @url.path
+      @env[QUERY_STRING]    = @url.query  if @url.query
+      @env[HTTP_HOST]       = @url.host   if @url.host
+      @env["HTTP_PORT"]     = @url.port   if @url.port
       @env[RACK_URL_SCHEME] = @url.scheme if @url.scheme
 
       super "forwarding to #{url}"
diff --git a/lib/rack/reloader.rb b/lib/rack/reloader.rb
index 296dd6a1..e23ed1fb 100644
--- a/lib/rack/reloader.rb
+++ b/lib/rack/reloader.rb
@@ -1,9 +1,13 @@
-#          Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com
-#       Rack::Reloader is subject to the terms of an MIT-style license.
-#      See COPYING or http://www.opensource.org/licenses/mit-license.php.
+# frozen_string_literal: true
+
+# Copyright (C) 2009-2018 Michael Fellinger <m.fellinger@gmail.com>
+# Rack::Reloader is subject to the terms of an MIT-style license.
+# See MIT-LICENSE or https://opensource.org/licenses/MIT.
 
 require 'pathname'
 
+require_relative 'core_ext/regexp'
+
 module Rack
 
   # High performant source reloader
@@ -20,6 +24,8 @@ module Rack
   # It is performing a check/reload cycle at the start of every request, but
   # also respects a cool down time, during which nothing will be done.
   class Reloader
+    using ::Rack::RegexpExtensions
+
     def initialize(app, cooldown = 10, backend = Stat)
       @app = app
       @cooldown = cooldown
@@ -69,7 +75,7 @@ module Rack
         paths = ['./', *$LOAD_PATH].uniq
 
         files.map{|file|
-          next if file =~ /\.(so|bundle)$/ # cannot reload compiled files
+          next if /\.(so|bundle)$/.match?(file) # cannot reload compiled files
 
           found, stat = figure_path(file, paths)
           next unless found && stat && mtime = stat.mtime
diff --git a/lib/rack/request.rb b/lib/rack/request.rb
index e2d2bb56..54ea86c4 100644
--- a/lib/rack/request.rb
+++ b/lib/rack/request.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
 require 'rack/utils'
 require 'rack/media_type'
 
+require_relative 'core_ext/regexp'
+
 module Rack
   # Rack::Request provides a convenient interface to a Rack
   # environment.  It is stateless, the environment +env+ passed to the
@@ -11,6 +15,19 @@ module Rack
   #   req.params["data"]
 
   class Request
+    using ::Rack::RegexpExtensions
+
+    class << self
+      attr_accessor :ip_filter
+    end
+
+    self.ip_filter = lambda { |ip| /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i.match?(ip) }
+    ALLOWED_SCHEMES = %w(https http).freeze
+    SCHEME_WHITELIST = ALLOWED_SCHEMES
+    if Object.respond_to?(:deprecate_constant)
+      deprecate_constant :SCHEME_WHITELIST
+    end
+
     def initialize(env)
       @params = nil
       super(env)
@@ -98,7 +115,7 @@ module Rack
 
     module Helpers
       # The set of form-data media-types. Requests that do not indicate
-      # one of the media types presents in this list will not be eligible
+      # one of the media types present in this list will not be eligible
       # for form-data / param parsing.
       FORM_DATA_MEDIA_TYPES = [
         'application/x-www-form-urlencoded',
@@ -106,7 +123,7 @@ module Rack
       ]
 
       # The set of media-types. Requests that do not indicate
-      # one of the media types presents in this list will not be eligible
+      # one of the media types present in this list will not be eligible
       # for param parsing like soap attachments or generic multiparts
       PARSEABLE_DATA_MEDIA_TYPES = [
         'multipart/related',
@@ -117,11 +134,11 @@ module Rack
       # to include the port in a generated URI.
       DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
 
-      HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'.freeze
-      HTTP_X_FORWARDED_PROTO  = 'HTTP_X_FORWARDED_PROTO'.freeze
-      HTTP_X_FORWARDED_HOST   = 'HTTP_X_FORWARDED_HOST'.freeze
-      HTTP_X_FORWARDED_PORT   = 'HTTP_X_FORWARDED_PORT'.freeze
-      HTTP_X_FORWARDED_SSL    = 'HTTP_X_FORWARDED_SSL'.freeze
+      HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
+      HTTP_X_FORWARDED_PROTO  = 'HTTP_X_FORWARDED_PROTO'
+      HTTP_X_FORWARDED_HOST   = 'HTTP_X_FORWARDED_HOST'
+      HTTP_X_FORWARDED_PORT   = 'HTTP_X_FORWARDED_PORT'
+      HTTP_X_FORWARDED_SSL    = 'HTTP_X_FORWARDED_SSL'
 
       def body;            get_header(RACK_INPUT)                         end
       def script_name;     get_header(SCRIPT_NAME).to_s                   end
@@ -157,10 +174,10 @@ module Rack
       def delete?;  request_method == DELETE  end
 
       # Checks the HTTP request method (or verb) to see if it was of type GET
-      def get?;     request_method == GET       end
+      def get?;     request_method == GET     end
 
       # Checks the HTTP request method (or verb) to see if it was of type HEAD
-      def head?;    request_method == HEAD      end
+      def head?;    request_method == HEAD    end
 
       # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
       def options?; request_method == OPTIONS end
@@ -188,10 +205,8 @@ module Rack
           'https'
         elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
           'https'
-        elsif get_header(HTTP_X_FORWARDED_SCHEME)
-          get_header(HTTP_X_FORWARDED_SCHEME)
-        elsif get_header(HTTP_X_FORWARDED_PROTO)
-          get_header(HTTP_X_FORWARDED_PROTO).split(',')[0]
+        elsif forwarded_scheme
+          forwarded_scheme
         else
           get_header(RACK_URL_SCHEME)
         end
@@ -208,7 +223,7 @@ module Rack
         string = get_header HTTP_COOKIE
 
         return hash if string == get_header(RACK_REQUEST_COOKIE_STRING)
-        hash.replace Utils.parse_cookies_header get_header HTTP_COOKIE
+        hash.replace Utils.parse_cookies_header string
         set_header(RACK_REQUEST_COOKIE_STRING, string)
         hash
       end
@@ -232,18 +247,23 @@ module Rack
 
       def host
         # Remove port number.
-        host_with_port.to_s.sub(/:\d+\z/, '')
+        h = host_with_port
+        if colon_index = h.index(":")
+          h[0, colon_index]
+        else
+          h
+        end
       end
 
       def port
-        if port = host_with_port.split(/:/)[1]
+        if port = extract_port(host_with_port)
           port.to_i
         elsif port = get_header(HTTP_X_FORWARDED_PORT)
           port.to_i
         elsif has_header?(HTTP_X_FORWARDED_HOST)
           DEFAULT_PORTS[scheme]
         elsif has_header?(HTTP_X_FORWARDED_PROTO)
-          DEFAULT_PORTS[get_header(HTTP_X_FORWARDED_PROTO).split(',')[0]]
+          DEFAULT_PORTS[extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO))]
         else
           get_header(SERVER_PORT).to_i
         end
@@ -260,8 +280,9 @@ module Rack
         return remote_addrs.first if remote_addrs.any?
 
         forwarded_ips = split_ip_addresses(get_header('HTTP_X_FORWARDED_FOR'))
+          .map { |ip| strip_port(ip) }
 
-        return reject_trusted_ip_addresses(forwarded_ips).last || get_header("REMOTE_ADDR")
+        return reject_trusted_ip_addresses(forwarded_ips).last || forwarded_ips.first || get_header("REMOTE_ADDR")
       end
 
       # The media type (type/subtype) portion of the CONTENT_TYPE header
@@ -386,12 +407,13 @@ module Rack
       #
       # <tt>env['rack.input']</tt> is not touched.
       def delete_param(k)
-        [ self.POST.delete(k), self.GET.delete(k) ].compact.first
+        post_value, get_value = self.POST.delete(k), self.GET.delete(k)
+        post_value || get_value
       end
 
       def base_url
         url = "#{scheme}://#{host}"
-        url << ":#{port}" if port != DEFAULT_PORTS[scheme]
+        url = "#{url}:#{port}" if port != DEFAULT_PORTS[scheme]
         url
       end
 
@@ -417,12 +439,12 @@ module Rack
       end
 
       def trusted_proxy?(ip)
-        ip =~ /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i
+        Rack::Request.ip_filter.call(ip)
       end
 
       # shortcut for <tt>request.params[key]</tt>
       def [](key)
-        if $verbose
+        if $VERBOSE
           warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead")
         end
 
@@ -433,7 +455,7 @@ module Rack
       #
       # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
       def []=(key, value)
-        if $verbose
+        if $VERBOSE
           warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead")
         end
 
@@ -464,7 +486,7 @@ module Rack
         Utils.default_query_parser
       end
 
-      def parse_query(qs, d='&')
+      def parse_query(qs, d = '&')
         query_parser.parse_nested_query(qs, d)
       end
 
@@ -476,9 +498,53 @@ module Rack
         ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
       end
 
+      def strip_port(ip_address)
+        # IPv6 format with optional port: "[2001:db8:cafe::17]:47011"
+        # returns: "2001:db8:cafe::17"
+        sep_start = ip_address.index('[')
+        sep_end = ip_address.index(']')
+        if (sep_start && sep_end)
+          return ip_address[sep_start + 1, sep_end - 1]
+        end
+
+        # IPv4 format with optional port: "192.0.2.43:47011"
+        # returns: "192.0.2.43"
+        sep = ip_address.index(':')
+        if (sep && ip_address.count(':') == 1)
+          return ip_address[0, sep]
+        end
+
+        ip_address
+      end
+
       def reject_trusted_ip_addresses(ip_addresses)
         ip_addresses.reject { |ip| trusted_proxy?(ip) }
       end
+
+      def forwarded_scheme
+        allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) ||
+        allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO)))
+      end
+
+      def allowed_scheme(header)
+        header if ALLOWED_SCHEMES.include?(header)
+      end
+
+      def extract_proto_header(header)
+        if header
+          if (comma_index = header.index(','))
+            header[0, comma_index]
+          else
+            header
+          end
+        end
+      end
+
+      def extract_port(uri)
+        if (colon_index = uri.index(':'))
+          uri[colon_index + 1, uri.length]
+        end
+      end
     end
 
     include Env
diff --git a/lib/rack/response.rb b/lib/rack/response.rb
index 9ac47aad..2e548cde 100644
--- a/lib/rack/response.rb
+++ b/lib/rack/response.rb
@@ -1,6 +1,9 @@
+# frozen_string_literal: true
+
 require 'rack/request'
 require 'rack/utils'
 require 'rack/body_proxy'
+require 'rack/simple_body_proxy'
 require 'rack/media_type'
 require 'time'
 
@@ -23,11 +26,15 @@ module Rack
     attr_reader :header
     alias headers header
 
-    CHUNKED = 'chunked'.freeze
+    CHUNKED = 'chunked'
+    STATUS_WITH_NO_ENTITY_BODY = {
+      204 => true,
+      304 => true
+    }.freeze
 
-    def initialize(body=[], status=200, header={})
+    def initialize(body = [], status = 200, header = {})
       @status = status.to_i
-      @header = Utils::HeaderHash.new.merge(header)
+      @header = Utils::HeaderHash.new(header)
 
       @writer  = lambda { |x| @body << x }
       @block   = nil
@@ -48,7 +55,7 @@ module Rack
       yield self  if block_given?
     end
 
-    def redirect(target, status=302)
+    def redirect(target, status = 302)
       self.status = status
       self.location = target
     end
@@ -60,13 +67,17 @@ module Rack
     def finish(&block)
       @block = block
 
-      if [204, 205, 304].include?(status.to_i)
+      if STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i)
         delete_header CONTENT_TYPE
         delete_header CONTENT_LENGTH
         close
         [status.to_i, header, []]
       else
-        [status.to_i, header, BodyProxy.new(self){}]
+        if @block.nil?
+          [status.to_i, header, SimpleBodyProxy.new(@body)]
+        else
+          [status.to_i, header, BodyProxy.new(self){}]
+        end
       end
     end
     alias to_a finish           # For *response
@@ -184,7 +195,7 @@ module Rack
         set_header SET_COOKIE, ::Rack::Utils.add_cookie_to_header(cookie_header, key, value)
       end
 
-      def delete_cookie(key, value={})
+      def delete_cookie(key, value = {})
         set_header SET_COOKIE, ::Rack::Utils.add_remove_cookie_to_header(get_header(SET_COOKIE), key, value)
       end
 
diff --git a/lib/rack/rewindable_input.rb b/lib/rack/rewindable_input.rb
index dd6b7843..97725091 100644
--- a/lib/rack/rewindable_input.rb
+++ b/lib/rack/rewindable_input.rb
@@ -1,4 +1,6 @@
 # -*- encoding: binary -*-
+# frozen_string_literal: true
+
 require 'tempfile'
 require 'rack/utils'
 
@@ -72,7 +74,7 @@ module Rack
         @unlinked = true
       end
 
-      buffer = ""
+      buffer = "".dup
       while @io.read(1024 * 4, buffer)
         entire_buffer_written_out = false
         while !entire_buffer_written_out
diff --git a/lib/rack/runtime.rb b/lib/rack/runtime.rb
index bb15bdb1..d2bca9e5 100644
--- a/lib/rack/runtime.rb
+++ b/lib/rack/runtime.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/utils'
 
 module Rack
@@ -8,8 +10,8 @@ module Rack
   # time, or before all the other middlewares to include time for them,
   # too.
   class Runtime
-    FORMAT_STRING = "%0.6f".freeze # :nodoc:
-    HEADER_NAME = "X-Runtime".freeze # :nodoc:
+    FORMAT_STRING = "%0.6f" # :nodoc:
+    HEADER_NAME = "X-Runtime" # :nodoc:
 
     def initialize(app, name = nil)
       @app = app
diff --git a/lib/rack/sendfile.rb b/lib/rack/sendfile.rb
index bdb7cf2f..51ba4db5 100644
--- a/lib/rack/sendfile.rb
+++ b/lib/rack/sendfile.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/file'
 require 'rack/body_proxy'
 
@@ -53,7 +55,7 @@ module Rack
   # that it maps to. The middleware performs a simple substitution on the
   # resulting path.
   #
-  # See Also: http://wiki.codemongers.com/NginxXSendfile
+  # See Also: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile
   #
   # === lighttpd
   #
@@ -99,7 +101,7 @@ module Rack
   # will be matched with case indifference.
 
   class Sendfile
-    def initialize(app, variation=nil, mappings=[])
+    def initialize(app, variation = nil, mappings = [])
       @app = app
       @variation = variation
       @mappings = mappings.map do |internal, external|
@@ -115,7 +117,8 @@ module Rack
           path = ::File.expand_path(body.to_path)
           if url = map_accel_path(env, path)
             headers[CONTENT_LENGTH] = '0'
-            headers[type] = url
+            # '?' must be percent-encoded because it is not query string but a part of path
+            headers[type] = ::Rack::Utils.escape_path(url).gsub('?', '%3F')
             obody = body
             body = Rack::BodyProxy.new([]) do
               obody.close if obody.respond_to?(:close)
@@ -147,11 +150,15 @@ module Rack
     end
 
     def map_accel_path(env, path)
-      if mapping = @mappings.find { |internal,_| internal =~ path }
+      if mapping = @mappings.find { |internal, _| internal =~ path }
         path.sub(*mapping)
       elsif mapping = env['HTTP_X_ACCEL_MAPPING']
-        internal, external = mapping.split('=', 2).map(&:strip)
-        path.sub(/^#{internal}/i, external)
+        mapping.split(',').map(&:strip).each do |m|
+          internal, external = m.split('=', 2).map(&:strip)
+          new_path = path.sub(/^#{internal}/i, external)
+          return new_path unless path == new_path
+        end
+        path
       end
     end
   end
diff --git a/lib/rack/server.rb b/lib/rack/server.rb
index 690f1096..f0bc1500 100644
--- a/lib/rack/server.rb
+++ b/lib/rack/server.rb
@@ -1,9 +1,14 @@
+# frozen_string_literal: true
+
 require 'optparse'
+require 'fileutils'
 
+require_relative 'core_ext/regexp'
 
 module Rack
 
   class Server
+    using ::Rack::RegexpExtensions
 
     class Options
       def parse!(args)
@@ -20,10 +25,6 @@ module Rack
             lineno += 1
           }
 
-          opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
-            options[:builder] = line
-          }
-
           opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
             options[:debug] = true
           }
@@ -46,7 +47,11 @@ module Rack
 
           opts.separator ""
           opts.separator "Rack options:"
-          opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick/mongrel)") { |s|
+          opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
+            options[:builder] = line
+          }
+
+          opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick)") { |s|
             options[:server] = s
           }
 
@@ -77,6 +82,24 @@ module Rack
           }
 
           opts.separator ""
+          opts.separator "Profiling options:"
+
+          opts.on("--heap HEAPFILE", "Build the application, then dump the heap to HEAPFILE") do |e|
+            options[:heapfile] = e
+          end
+
+          opts.on("--profile PROFILE", "Dump CPU or Memory profile to PROFILE (defaults to a tempfile)") do |e|
+            options[:profile_file] = e
+          end
+
+          opts.on("--profile-mode MODE", "Profile mode (cpu|wall|object)") do |e|
+            { cpu: true, wall: true, object: true }.fetch(e.to_sym) do
+              raise OptionParser::InvalidOption, "unknown profile mode: #{e}"
+            end
+            options[:profile_mode] = e.to_sym
+          end
+
+          opts.separator ""
           opts.separator "Common options:"
 
           opts.on_tail("-h", "-?", "--help", "Show this message") do
@@ -113,7 +136,7 @@ module Rack
 
             has_options = false
             server.valid_options.each do |name, description|
-              next if name.to_s.match(/^(Host|Port)[^a-zA-Z]/) # ignore handler's host and port options, we do our own.
+              next if /^(Host|Port)[^a-zA-Z]/.match?(name.to_s) # ignore handler's host and port options, we do our own.
               info << "  -O %-21s %s" % [name, description]
               has_options = true
             end
@@ -151,7 +174,9 @@ module Rack
 
     # Options may include:
     # * :app
-    #     a rack application to run (overrides :config)
+    #     a rack application to run (overrides :config and :builder)
+    # * :builder
+    #     a string to evaluate a Rack::Builder from
     # * :config
     #     a rackup configuration file path to load (.ru)
     # * :environment
@@ -181,6 +206,14 @@ module Rack
     #     add given paths to $LOAD_PATH
     # * :require
     #     require the given libraries
+    #
+    # Additional options for profiling app initialization include:
+    # * :heapfile
+    #     location for ObjectSpace.dump_all to write the output to
+    # * :profile_file
+    #     location for CPU/Memory (StackProf) profile output (defaults to a tempfile)
+    # * :profile_mode
+    #     StackProf profile mode (cpu|wall|object)
     def initialize(options = nil)
       @ignore_options = []
 
@@ -205,12 +238,12 @@ module Rack
       default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
 
       {
-        :environment => environment,
-        :pid         => nil,
-        :Port        => 9292,
-        :Host        => default_host,
-        :AccessLog   => [],
-        :config      => "config.ru"
+        environment: environment,
+        pid: nil,
+        Port: 9292,
+        Host: default_host,
+        AccessLog: [],
+        config: "config.ru"
       }
     end
 
@@ -221,12 +254,12 @@ module Rack
     class << self
       def logging_middleware
         lambda { |server|
-          server.server.name =~ /CGI/ || server.options[:quiet] ? nil : [Rack::CommonLogger, $stderr]
+          /CGI/.match?(server.server.name) || server.options[:quiet] ? nil : [Rack::CommonLogger, $stderr]
         }
       end
 
       def default_middleware_by_environment
-        m = Hash.new {|h,k| h[k] = []}
+        m = Hash.new {|h, k| h[k] = []}
         m["deployment"] = [
           [Rack::ContentLength],
           [Rack::Chunked],
@@ -279,7 +312,9 @@ module Rack
 
       # Touch the wrapped app, so that the config.ru is loaded before
       # daemonization (i.e. before chdir, etc).
-      wrapped_app
+      handle_profiling(options[:heapfile], options[:profile_mode], options[:profile_file]) do
+        wrapped_app
+      end
 
       daemonize_app if options[:daemonize]
 
@@ -320,6 +355,44 @@ module Rack
         app
       end
 
+      def handle_profiling(heapfile, profile_mode, filename)
+        if heapfile
+          require "objspace"
+          ObjectSpace.trace_object_allocations_start
+          yield
+          GC.start
+          ::File.open(heapfile, "w") { |f| ObjectSpace.dump_all(output: f) }
+          exit
+        end
+
+        if profile_mode
+          require "stackprof"
+          require "tempfile"
+
+          make_profile_name(filename) do |filename|
+            ::File.open(filename, "w") do |f|
+              StackProf.run(mode: profile_mode, out: f) do
+                yield
+              end
+              puts "Profile written to: #{filename}"
+            end
+          end
+          exit
+        end
+
+        yield
+      end
+
+      def make_profile_name(filename)
+        if filename
+          yield filename
+        else
+          ::Dir::Tmpname.create("profile.dump") do |tmpname, _, _|
+            yield tmpname
+          end
+        end
+      end
+
       def build_app_from_string
         Rack::Builder.new_from_string(self.options[:builder])
       end
@@ -359,7 +432,7 @@ module Rack
 
       def write_pid
         ::File.open(options[:pid], ::File::CREAT | ::File::EXCL | ::File::WRONLY ){ |f| f.write("#{Process.pid}") }
-        at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) }
+        at_exit { ::FileUtils.rm_f(options[:pid]) }
       rescue Errno::EEXIST
         check_pid!
         retry
diff --git a/lib/rack/session/abstract/id.rb b/lib/rack/session/abstract/id.rb
index 204bdb34..c9258644 100644
--- a/lib/rack/session/abstract/id.rb
+++ b/lib/rack/session/abstract/id.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
 # bugrep: Andreas Zehnder
 
@@ -15,9 +17,23 @@ module Rack
       # SessionHash is responsible to lazily load the session from store.
 
       class SessionHash
+        using Module.new {
+          refine Hash do
+            def transform_keys(&block)
+              hash = {}
+              each do |key, value|
+                hash[block.call(key)] = value
+              end
+              hash
+            end
+          end
+        } unless {}.respond_to?(:transform_keys)
+
         include Enumerable
         attr_writer :id
 
+        Unspecified = Object.new
+
         def self.find(req)
           req.get_header RACK_SESSION
         end
@@ -54,7 +70,15 @@ module Rack
           load_for_read!
           @data[key.to_s]
         end
-        alias :fetch :[]
+
+        def fetch(key, default = Unspecified, &block)
+          load_for_read!
+          if default == Unspecified
+            @data.fetch(key.to_s, &block)
+          else
+            @data.fetch(key.to_s, default, &block)
+          end
+        end
 
         def has_key?(key)
           load_for_read!
@@ -150,11 +174,7 @@ module Rack
         end
 
         def stringify_keys(other)
-          hash = {}
-          other.each do |key, value|
-            hash[key.to_s] = value
-          end
-          hash
+          other.transform_keys(&:to_s)
         end
       end
 
@@ -167,7 +187,7 @@ module Rack
       # * :key determines the name of the cookie, by default it is
       #   'rack.session'
       # * :path, :domain, :expire_after, :secure, and :httponly set the related
-      #   cookie options as by Rack::Response#add_cookie
+      #   cookie options as by Rack::Response#set_cookie
       # * :skip will not a set a cookie in the response nor update the session state
       # * :defer will not set a cookie in the response but still update the session
       #   state if it is used with a backend
@@ -189,22 +209,22 @@ module Rack
 
       class Persisted
         DEFAULT_OPTIONS = {
-          :key =>           RACK_SESSION,
-          :path =>          '/',
-          :domain =>        nil,
-          :expire_after =>  nil,
-          :secure =>        false,
-          :httponly =>      true,
-          :defer =>         false,
-          :renew =>         false,
-          :sidbits =>       128,
-          :cookie_only =>   true,
-          :secure_random => ::SecureRandom
-        }
+          key: RACK_SESSION,
+          path: '/',
+          domain: nil,
+          expire_after: nil,
+          secure: false,
+          httponly: true,
+          defer: false,
+          renew: false,
+          sidbits: 128,
+          cookie_only: true,
+          secure_random: ::SecureRandom
+        }.freeze
 
         attr_reader :key, :default_options, :sid_secure
 
-        def initialize(app, options={})
+        def initialize(app, options = {})
           @app = app
           @default_options = self.class::DEFAULT_OPTIONS.merge(options)
           @key = @default_options.delete(:key)
@@ -216,7 +236,7 @@ module Rack
           context(env)
         end
 
-        def context(env, app=@app)
+        def context(env, app = @app)
           req = make_request env
           prepare_session(req)
           status, headers, body = app.call(req.env)
@@ -339,7 +359,7 @@ module Rack
 
           session.send(:load!) unless loaded_session?(session)
           session_id ||= session.id
-          session_data = session.to_hash.delete_if { |k,v| v.nil? }
+          session_data = session.to_hash.delete_if { |k, v| v.nil? }
 
           if not data = write_session(req, session_id, session_data, options)
             req.get_header(RACK_ERRORS).puts("Warning! #{self.class.name} failed to save session. Content dropped.")
@@ -398,7 +418,7 @@ module Rack
 
       class ID < Persisted
         def self.inherited(klass)
-          k = klass.ancestors.find { |kl| kl.superclass == ID }
+          k = klass.ancestors.find { |kl| kl.respond_to?(:superclass) && kl.superclass == ID }
           unless k.instance_variable_defined?(:"@_rack_warned")
             warn "#{klass} is inheriting from #{ID}.  Inheriting from #{ID} is deprecated, please inherit from #{Persisted} instead" if $VERBOSE
             k.instance_variable_set(:"@_rack_warned", true)
diff --git a/lib/rack/session/cookie.rb b/lib/rack/session/cookie.rb
index 71bb96f4..3c067d7b 100644
--- a/lib/rack/session/cookie.rb
+++ b/lib/rack/session/cookie.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'openssl'
 require 'zlib'
 require 'rack/request'
@@ -103,7 +105,7 @@ module Rack
 
       attr_reader :coder
 
-      def initialize(app, options={})
+      def initialize(app, options = {})
         @secrets = options.values_at(:secret, :old_secret).compact
         @hmac = options.fetch(:hmac, OpenSSL::Digest::SHA1)
 
@@ -116,8 +118,8 @@ module Rack
 
         Called from: #{caller[0]}.
         MSG
-        @coder  = options[:coder] ||= Base64::Marshal.new
-        super(app, options.merge!(:cookie_only => true))
+        @coder = options[:coder] ||= Base64::Marshal.new
+        super(app, options.merge!(cookie_only: true))
       end
 
       private
@@ -137,9 +139,7 @@ module Rack
           session_data = request.cookies[@key]
 
           if @secrets.size > 0 && session_data
-            digest, session_data = session_data.reverse.split("--", 2)
-            digest.reverse! if digest
-            session_data.reverse! if session_data
+            session_data, _, digest = session_data.rpartition('--')
             session_data = nil unless digest_match?(session_data, digest)
           end
 
@@ -147,7 +147,7 @@ module Rack
         end
       end
 
-      def persistent_session_id!(data, sid=nil)
+      def persistent_session_id!(data, sid = nil)
         data ||= {}
         data["session_id"] ||= sid || generate_sid
         data
diff --git a/lib/rack/session/memcache.rb b/lib/rack/session/memcache.rb
index 4cf5ea09..dd587633 100644
--- a/lib/rack/session/memcache.rb
+++ b/lib/rack/session/memcache.rb
@@ -1,7 +1,10 @@
+# frozen_string_literal: true
+
 # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
 
 require 'rack/session/abstract/id'
 require 'memcache'
+require 'rack/core_ext/regexp'
 
 module Rack
   module Session
@@ -20,18 +23,20 @@ module Rack
     # a full description of behaviour, please see memcache's documentation.
 
     class Memcache < Abstract::ID
+      using ::Rack::RegexpExtensions
+
       attr_reader :mutex, :pool
 
       DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
-        :namespace => 'rack:session',
-        :memcache_server => 'localhost:11211'
+        namespace: 'rack:session',
+        memcache_server: 'localhost:11211'
 
-      def initialize(app, options={})
+      def initialize(app, options = {})
         super
 
         @mutex = Mutex.new
         mserv = @default_options[:memcache_server]
-        mopts = @default_options.reject{|k,v| !MemCache::DEFAULT_OPTIONS.include? k }
+        mopts = @default_options.reject{|k, v| !MemCache::DEFAULT_OPTIONS.include? k }
 
         @pool = options[:cache] || MemCache.new(mserv, mopts)
         unless @pool.active? and @pool.servers.any?(&:alive?)
@@ -50,7 +55,7 @@ module Rack
         with_lock(env) do
           unless sid and session = @pool.get(sid)
             sid, session = generate_sid, {}
-            unless /^STORED/ =~ @pool.add(sid, session)
+            unless /^STORED/.match?(@pool.add(sid, session))
               raise "Session collision on '#{sid.inspect}'"
             end
           end
diff --git a/lib/rack/session/pool.rb b/lib/rack/session/pool.rb
index 4c9c25c7..2e1f867f 100644
--- a/lib/rack/session/pool.rb
+++ b/lib/rack/session/pool.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
 # THANKS:
 #   apeiros, for session id generation, expiry setup, and threadiness
@@ -26,9 +28,9 @@ module Rack
 
     class Pool < Abstract::Persisted
       attr_reader :mutex, :pool
-      DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :drop => false
+      DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge drop: false
 
-      def initialize(app, options={})
+      def initialize(app, options = {})
         super
         @pool = Hash.new
         @mutex = Mutex.new
diff --git a/lib/rack/show_exceptions.rb b/lib/rack/show_exceptions.rb
index ca86b2b2..843af607 100644
--- a/lib/rack/show_exceptions.rb
+++ b/lib/rack/show_exceptions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'ostruct'
 require 'erb'
 require 'rack/request'
@@ -46,7 +48,7 @@ module Rack
     end
 
     def prefers_plaintext?(env)
-      !accepts_html(env)
+      !accepts_html?(env)
     end
 
     def accepts_html?(env)
@@ -55,7 +57,7 @@ module Rack
     private :accepts_html?
 
     def dump_exception(exception)
-      string = "#{exception.class}: #{exception.message}\n"
+      string = "#{exception.class}: #{exception.message}\n".dup
       string << exception.backtrace.map { |l| "\t#{l}" }.join("\n")
       string
     end
@@ -77,13 +79,13 @@ module Rack
           frame.function = $4
 
           begin
-            lineno = frame.lineno-1
+            lineno = frame.lineno - 1
             lines = ::File.readlines(frame.filename)
-            frame.pre_context_lineno = [lineno-CONTEXT, 0].max
+            frame.pre_context_lineno = [lineno - CONTEXT, 0].max
             frame.pre_context = lines[frame.pre_context_lineno...lineno]
             frame.context_line = lines[lineno].chomp
-            frame.post_context_lineno = [lineno+CONTEXT, lines.size].min
-            frame.post_context = lines[lineno+1..frame.post_context_lineno]
+            frame.post_context_lineno = [lineno + CONTEXT, lines.size].min
+            frame.post_context = lines[lineno + 1..frame.post_context_lineno]
           rescue
           end
 
@@ -93,7 +95,11 @@ module Rack
         end
       }.compact
 
-      TEMPLATE.result(binding)
+      template.result(binding)
+    end
+
+    def template
+      TEMPLATE
     end
 
     def h(obj)                  # :nodoc:
@@ -107,8 +113,8 @@ module Rack
 
     # :stopdoc:
 
-    # adapted from Django <djangoproject.com>
-    # Copyright (c) 2005, the Lawrence Journal-World
+    # adapted from Django <www.djangoproject.com>
+    # Copyright (c) Django Software Foundation and individual contributors.
     # Used under the modified BSD license:
     # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
     TEMPLATE = ERB.new(<<-'HTML'.gsub(/^      /, ''))
@@ -363,7 +369,7 @@ module Rack
                 <% env.sort_by { |k, v| k.to_s }.each { |key, val| %>
                 <tr>
                   <td><%=h key %></td>
-                  <td class="code"><div><%=h val %></div></td>
+                  <td class="code"><div><%=h val.inspect %></div></td>
                 </tr>
                 <% } %>
             </tbody>
diff --git a/lib/rack/show_status.rb b/lib/rack/show_status.rb
index 54db8f47..3fdfca5e 100644
--- a/lib/rack/show_status.rb
+++ b/lib/rack/show_status.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'erb'
 require 'rack/request'
 require 'rack/utils'
@@ -52,8 +54,8 @@ module Rack
 
     # :stopdoc:
 
-# adapted from Django <djangoproject.com>
-# Copyright (c) 2005, the Lawrence Journal-World
+# adapted from Django <www.djangoproject.com>
+# Copyright (c) Django Software Foundation and individual contributors.
 # Used under the modified BSD license:
 # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
 TEMPLATE = <<'HTML'
diff --git a/lib/rack/simple_body_proxy.rb b/lib/rack/simple_body_proxy.rb
new file mode 100644
index 00000000..fe007c4c
--- /dev/null
+++ b/lib/rack/simple_body_proxy.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Rack
+  class SimpleBodyProxy
+    def initialize(body)
+      @body = body
+    end
+
+    def each(&blk)
+      @body.each(&blk)
+    end
+  end
+end
diff --git a/lib/rack/static.rb b/lib/rack/static.rb
index f59a205d..24c40505 100644
--- a/lib/rack/static.rb
+++ b/lib/rack/static.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
 require "rack/file"
 require "rack/utils"
 
+require_relative 'core_ext/regexp'
+
 module Rack
 
   # The Rack::Static middleware intercepts requests for static files
@@ -82,8 +86,9 @@ module Rack
   #         ]
   #
   class Static
+    using ::Rack::RegexpExtensions
 
-    def initialize(app, options={})
+    def initialize(app, options = {})
       @app = app
       @urls = options[:urls] || ["/favicon.ico"]
       @index = options[:index]
@@ -93,13 +98,13 @@ module Rack
       # HTTP Headers
       @header_rules = options[:header_rules] || []
       # Allow for legacy :cache_control option while prioritizing global header_rules setting
-      @header_rules.unshift([:all, {CACHE_CONTROL => options[:cache_control]}]) if options[:cache_control]
+      @header_rules.unshift([:all, { CACHE_CONTROL => options[:cache_control] }]) if options[:cache_control]
 
       @file_server = Rack::File.new(root)
     end
 
     def add_index_root?(path)
-      @index && path.end_with?('/'.freeze)
+      @index && route_file(path) && path.end_with?('/'.freeze)
     end
 
     def overwrite_file_path(path)
@@ -120,7 +125,7 @@ module Rack
       if can_serve(path)
         if overwrite_file_path(path)
           env[PATH_INFO] = (add_index_root?(path) ? path + @index : @urls[path])
-        elsif @gzip && env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/
+        elsif @gzip && /\bgzip\b/.match?(env['HTTP_ACCEPT_ENCODING'])
           path = env[PATH_INFO]
           env[PATH_INFO] += '.gz'
           response = @file_server.call(env)
@@ -157,14 +162,14 @@ module Rack
         when :all
           true
         when :fonts
-          path =~ /\.(?:ttf|otf|eot|woff2|woff|svg)\z/
+          /\.(?:ttf|otf|eot|woff2|woff|svg)\z/.match?(path)
         when String
           path = ::Rack::Utils.unescape(path)
           path.start_with?(rule) || path.start_with?('/' + rule)
         when Array
-          path =~ /\.(#{rule.join('|')})\z/
+          /\.(#{rule.join('|')})\z/.match?(path)
         when Regexp
-          path =~ rule
+          rule.match?(path)
         else
           false
         end
diff --git a/lib/rack/tempfile_reaper.rb b/lib/rack/tempfile_reaper.rb
index d8299806..73b6c1c8 100644
--- a/lib/rack/tempfile_reaper.rb
+++ b/lib/rack/tempfile_reaper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rack/body_proxy'
 
 module Rack
diff --git a/lib/rack/urlmap.rb b/lib/rack/urlmap.rb
index 510b4b50..c5d9c44f 100644
--- a/lib/rack/urlmap.rb
+++ b/lib/rack/urlmap.rb
@@ -1,3 +1,7 @@
+# frozen_string_literal: true
+
+require 'set'
+
 module Rack
   # Rack::URLMap takes a hash mapping urls or paths to apps, and
   # dispatches accordingly.  Support for HTTP/1.1 host names exists if
@@ -20,9 +24,11 @@ module Rack
     end
 
     def remap(map)
+      @known_hosts = Set[]
       @mapping = map.map { |location, app|
         if location =~ %r{\Ahttps?://(.*?)(/.*)}
           host, location = $1, $2
+          @known_hosts << host
         else
           host = nil
         end
@@ -50,10 +56,13 @@ module Rack
       is_same_server = casecmp?(http_host, server_name) ||
                        casecmp?(http_host, "#{server_name}:#{server_port}")
 
+      is_host_known = @known_hosts.include? http_host
+
       @mapping.each do |host, location, match, app|
         unless casecmp?(http_host, host) \
             || casecmp?(server_name, host) \
-            || (!host && is_same_server)
+            || (!host && is_same_server) \
+            || (!host && !is_host_known) # If we don't have a matching host, default to the first without a specified host
           next
         end
 
@@ -68,7 +77,7 @@ module Rack
         return app.call(env)
       end
 
-      [404, {CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
+      [404, { CONTENT_TYPE => "text/plain", "X-Cascade" => "pass" }, ["Not Found: #{path}"]]
 
     ensure
       env[PATH_INFO]   = path
diff --git a/lib/rack/utils.rb b/lib/rack/utils.rb
index 7b842125..43d70a85 100644
--- a/lib/rack/utils.rb
+++ b/lib/rack/utils.rb
@@ -1,4 +1,6 @@
 # -*- encoding: binary -*-
+# frozen_string_literal: true
+
 require 'uri'
 require 'fileutils'
 require 'set'
@@ -6,11 +8,15 @@ require 'tempfile'
 require 'rack/query_parser'
 require 'time'
 
+require_relative 'core_ext/regexp'
+
 module Rack
   # Rack::Utils contains a grab-bag of useful methods for writing web
   # applications adopted from all kinds of Ruby libraries.
 
   module Utils
+    using ::Rack::RegexpExtensions
+
     ParameterTypeError = QueryParser::ParameterTypeError
     InvalidParameterError = QueryParser::InvalidParameterError
     DEFAULT_SEP = QueryParser::DEFAULT_SEP
@@ -118,7 +124,7 @@ module Rack
       when Hash
         value.map { |k, v|
           build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
-        }.reject(&:empty?).join('&')
+        }.delete_if(&:empty?).join('&')
       when nil
         prefix
       else
@@ -175,27 +181,26 @@ module Rack
       # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
 
       expanded_accept_encoding =
-        accept_encoding.map { |m, q|
+        accept_encoding.each_with_object([]) do |(m, q), list|
           if m == "*"
-            (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
+            (available_encodings - accept_encoding.map(&:first))
+              .each { |m2| list << [m2, q] }
           else
-            [[m, q]]
+            list << [m, q]
           end
-        }.inject([]) { |mem, list|
-          mem + list
-        }
+        end
 
-      encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }
+      encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map!(&:first)
 
       unless encoding_candidates.include?("identity")
         encoding_candidates.push("identity")
       end
 
-      expanded_accept_encoding.each { |m, q|
+      expanded_accept_encoding.each do |m, q|
         encoding_candidates.delete(m) if q == 0.0
-      }
+      end
 
-      return (encoding_candidates & available_encodings)[0]
+      (encoding_candidates & available_encodings)[0]
     end
     module_function :select_best_encoding
 
@@ -211,7 +216,7 @@ module Rack
       #   precede those with less specific.  Ordering with respect to other
       #   attributes (e.g., Domain) is unspecified.
       cookies = parse_query(header, ';,') { |s| unescape(s) rescue s }
-      cookies.each_with_object({}) { |(k,v), hash| hash[k] = Array === v ? v.first : v }
+      cookies.each_with_object({}) { |(k, v), hash| hash[k] = Array === v ? v.first : v }
     end
     module_function :parse_cookies_header
 
@@ -221,41 +226,19 @@ module Rack
         domain  = "; domain=#{value[:domain]}"   if value[:domain]
         path    = "; path=#{value[:path]}"       if value[:path]
         max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
-        # There is an RFC mess in the area of date formatting for Cookies. Not
-        # only are there contradicting RFCs and examples within RFC text, but
-        # there are also numerous conflicting names of fields and partially
-        # cross-applicable specifications.
-        #
-        # These are best described in RFC 2616 3.3.1. This RFC text also
-        # specifies that RFC 822 as updated by RFC 1123 is preferred. That is a
-        # fixed length format with space-date delimited fields.
-        #
-        # See also RFC 1123 section 5.2.14.
-        #
-        # RFC 6265 also specifies "sane-cookie-date" as RFC 1123 date, defined
-        # in RFC 2616 3.3.1. RFC 6265 also gives examples that clearly denote
-        # the space delimited format. These formats are compliant with RFC 2822.
-        #
-        # For reference, all involved RFCs are:
-        # RFC 822
-        # RFC 1123
-        # RFC 2109
-        # RFC 2616
-        # RFC 2822
-        # RFC 2965
-        # RFC 6265
-        expires = "; expires=" +
-          rfc2822(value[:expires].clone.gmtime) if value[:expires]
+        expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
         secure = "; secure"  if value[:secure]
         httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
         same_site =
           case value[:same_site]
           when false, nil
             nil
+          when :none, 'None', :None
+            '; SameSite=None'
           when :lax, 'Lax', :Lax
-            '; SameSite=Lax'.freeze
+            '; SameSite=Lax'
           when true, :strict, 'Strict', :Strict
-            '; SameSite=Strict'.freeze
+            '; SameSite=Strict'
           else
             raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
           end
@@ -295,15 +278,15 @@ module Rack
         cookies = header
       end
 
-      cookies.reject! { |cookie|
-        if value[:domain]
-          cookie =~ /\A#{escape(key)}=.*domain=#{value[:domain]}/
-        elsif value[:path]
-          cookie =~ /\A#{escape(key)}=.*path=#{value[:path]}/
-        else
-          cookie =~ /\A#{escape(key)}=/
-        end
-      }
+      regexp = if value[:domain]
+                 /\A#{escape(key)}=.*domain=#{value[:domain]}/
+               elsif value[:path]
+                 /\A#{escape(key)}=.*path=#{value[:path]}/
+               else
+                 /\A#{escape(key)}=/
+               end
+
+      cookies.reject! { |cookie| regexp.match? cookie }
 
       cookies.join("\n")
     end
@@ -321,9 +304,9 @@ module Rack
       new_header = make_delete_cookie_header(header, key, value)
 
       add_cookie_to_header(new_header, key,
-                 {:value => '', :path => nil, :domain => nil,
-                   :max_age => '0',
-                   :expires => Time.at(0) }.merge(value))
+                 { value: '', path: nil, domain: nil,
+                   max_age: '0',
+                   expires: Time.at(0) }.merge(value))
 
     end
     module_function :add_remove_cookie_to_header
@@ -364,7 +347,7 @@ module Rack
       ranges = []
       $1.split(/,\s*/).each do |range_spec|
         return nil  unless range_spec =~ /(\d*)-(\d*)/
-        r0,r1 = $1, $2
+        r0, r1 = $1, $2
         if r0.empty?
           return nil  if r1.empty?
           # suffix-byte-range-spec, represents trailing suffix of file
@@ -378,7 +361,7 @@ module Rack
           else
             r1 = r1.to_i
             return nil  if r1 < r0  # backwards range is syntactically invalid
-            r1 = size-1  if r1 >= size
+            r1 = size - 1  if r1 >= size
           end
         end
         ranges << (r0..r1)  if r0 <= r1
@@ -399,7 +382,7 @@ module Rack
       l = a.unpack("C*")
 
       r, i = 0, -1
-      b.each_byte { |v| r |= v ^ l[i+=1] }
+      b.each_byte { |v| r |= v ^ l[i += 1] }
       r == 0
     end
     module_function :secure_compare
@@ -425,19 +408,17 @@ module Rack
         self.class.new(@for, app)
       end
 
-      def context(env, app=@app)
+      def context(env, app = @app)
         recontext(app).call(env)
       end
     end
 
     # A case-insensitive Hash that preserves the original case of a
     # header when set.
-    class HeaderHash < Hash
-      def self.new(hash={})
-        HeaderHash === hash ? hash : super(hash)
-      end
-
-      def initialize(hash={})
+    #
+    # @api private
+    class HeaderHash < Hash # :nodoc:
+      def initialize(hash = {})
         super()
         @names = {}
         hash.each { |k, v| self[k] = v }
@@ -457,7 +438,7 @@ module Rack
 
       def to_hash
         hash = {}
-        each { |k,v| hash[k] = v }
+        each { |k, v| hash[k] = v }
         hash
       end
 
@@ -510,13 +491,14 @@ module Rack
 
     # Every standard HTTP code mapped to the appropriate message.
     # Generated with:
-    # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
-    #   ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
-    #             puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
+    #   curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
+    #     ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
+    #               puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
     HTTP_STATUS_CODES = {
       100 => 'Continue',
       101 => 'Switching Protocols',
       102 => 'Processing',
+      103 => 'Early Hints',
       200 => 'OK',
       201 => 'Created',
       202 => 'Accepted',
@@ -557,6 +539,7 @@ module Rack
       422 => 'Unprocessable Entity',
       423 => 'Locked',
       424 => 'Failed Dependency',
+      425 => 'Too Early',
       426 => 'Upgrade Required',
       428 => 'Precondition Required',
       429 => 'Too Many Requests',
@@ -571,12 +554,13 @@ module Rack
       506 => 'Variant Also Negotiates',
       507 => 'Insufficient Storage',
       508 => 'Loop Detected',
+      509 => 'Bandwidth Limit Exceeded',
       510 => 'Not Extended',
       511 => 'Network Authentication Required'
     }
 
     # Responses with HTTP status codes that should not have an entity body
-    STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 205 << 304)
+    STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
 
     SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
       [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
@@ -584,7 +568,7 @@ module Rack
 
     def status_code(status)
       if status.is_a?(Symbol)
-        SYMBOL_TO_STATUS_CODE[status] || 500
+        SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
       else
         status.to_i
       end
@@ -605,11 +589,11 @@ module Rack
 
       clean.unshift '/' if parts.empty? || parts.first.empty?
 
-      ::File.join(*clean)
+      ::File.join clean
     end
     module_function :clean_path_info
 
-    NULL_BYTE = "\0".freeze
+    NULL_BYTE = "\0"
 
     def valid_path?(path)
       path.valid_encoding? && !path.include?(NULL_BYTE)
diff --git a/rack.gemspec b/rack.gemspec
index 1b412793..f7b13b17 100644
--- a/rack.gemspec
+++ b/rack.gemspec
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
+
 Gem::Specification.new do |s|
-  s.name            = "rack"
+  s.name = "rack"
   s.version = File.read('lib/rack.rb')[/RELEASE += +([\"\'])([\d][\w\.]+)\1/, 2]
   s.platform        = Gem::Platform::RUBY
   s.summary         = "a modular Ruby webserver interface"
@@ -12,26 +14,31 @@ the simplest way possible, it unifies and distills the API for web
 servers, web frameworks, and software in between (the so-called
 middleware) into a single method call.
 
-Also see http://rack.github.io/.
+Also see https://rack.github.io/.
 EOF
 
-  s.files           = Dir['{bin/*,contrib/*,example/*,lib/**/*,test/**/*}'] +
-                        %w(COPYING rack.gemspec Rakefile README.rdoc SPEC)
+  s.files           = Dir['{bin/*,contrib/*,example/*,lib/**/*}'] +
+                        %w(MIT-LICENSE rack.gemspec Rakefile README.rdoc SPEC)
   s.bindir          = 'bin'
-  s.executables     << 'rackup'
-  s.require_path    = 'lib'
-  s.extra_rdoc_files = ['README.rdoc', 'HISTORY.md']
-  s.test_files      = Dir['test/spec_*.rb']
+  s.executables << 'rackup'
+  s.require_path = 'lib'
+  s.extra_rdoc_files = ['README.rdoc', 'CHANGELOG.md']
 
-  s.author          = 'Christian Neukirchen'
-  s.email           = 'chneukirchen@gmail.com'
-  s.homepage        = 'http://rack.github.io/'
+  s.author          = 'Leah Neukirchen'
+  s.email           = 'leah@vuxu.org'
+  s.homepage        = 'https://rack.github.io/'
   s.required_ruby_version = '>= 2.2.2'
-
-  s.add_dependency 'json'
+  s.metadata              = {
+    "bug_tracker_uri"   => "https://github.com/rack/rack/issues",
+    "changelog_uri"     => "https://github.com/rack/rack/blob/master/CHANGELOG.md",
+    "documentation_uri" => "https://rubydoc.info/github/rack/rack",
+    "homepage_uri"      => "https://rack.github.io",
+    "mailing_list_uri"  => "https://groups.google.com/forum/#!forum/rack-devel",
+    "source_code_uri"   => "https://github.com/rack/rack"
+  }
 
   s.add_development_dependency 'minitest', "~> 5.0"
   s.add_development_dependency 'minitest-sprint'
-  s.add_development_dependency 'concurrent-ruby'
+  s.add_development_dependency 'minitest-global_expectations'
   s.add_development_dependency 'rake'
 end
diff --git a/test/builder/an_underscore_app.rb b/test/builder/an_underscore_app.rb
index 7ce1a0cc..f58a2be5 100644
--- a/test/builder/an_underscore_app.rb
+++ b/test/builder/an_underscore_app.rb
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
+
 class AnUnderscoreApp
   def self.call(env)
-    [200, {'Content-Type' => 'text/plain'}, ['OK']]
+    [200, { 'Content-Type' => 'text/plain' }, ['OK']]
   end
 end
diff --git a/test/builder/anything.rb b/test/builder/anything.rb
deleted file mode 100644
index c07f82cd..00000000
--- a/test/builder/anything.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class Anything
-  def self.call(env)
-    [200, {'Content-Type' => 'text/plain'}, ['OK']]
-  end
-end
diff --git a/test/builder/bom.ru b/test/builder/bom.ru
new file mode 100644
index 00000000..5740f9a1
--- /dev/null
+++ b/test/builder/bom.ru
@@ -0,0 +1 @@
+run -> (env) { [200, { 'Content-Type' => 'text/plain' }, ['OK']] }
diff --git a/test/builder/comment.ru b/test/builder/comment.ru
index 0722f0a0..894ba5d0 100644
--- a/test/builder/comment.ru
+++ b/test/builder/comment.ru
@@ -1,4 +1,6 @@
+# frozen_string_literal: true
+
 =begin
 
 =end
-run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
+run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ['OK']] }
diff --git a/test/builder/end.ru b/test/builder/end.ru
index 7f36d8cb..dd8d45a9 100644
--- a/test/builder/end.ru
+++ b/test/builder/end.ru
@@ -1,4 +1,6 @@
-run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
+# frozen_string_literal: true
+
+run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ['OK']] }
 __END__
 Should not be evaluated
 Neither should
diff --git a/test/builder/line.ru b/test/builder/line.ru
index f4c84ade..9ad88986 100644
--- a/test/builder/line.ru
+++ b/test/builder/line.ru
@@ -1 +1,3 @@
-run lambda{ |env| [200, {'Content-Type' => 'text/plain'}, [__LINE__.to_s]] }
+# frozen_string_literal: true
+
+run lambda{ |env| [200, { 'Content-Type' => 'text/plain' }, [__LINE__.to_s]] }
diff --git a/test/builder/options.ru b/test/builder/options.ru
index 4af32440..dca48fd9 100644
--- a/test/builder/options.ru
+++ b/test/builder/options.ru
@@ -1,2 +1,4 @@
+# frozen_string_literal: true
+
 #\ -d -p 2929 --env test
-run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
+run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ['OK']] }
diff --git a/test/cgi/rackup_stub.rb b/test/cgi/rackup_stub.rb
index a216cdc3..5f0e4365 100755
--- a/test/cgi/rackup_stub.rb
+++ b/test/cgi/rackup_stub.rb
@@ -1,5 +1,5 @@
 #!/usr/bin/env ruby
-# -*- ruby -*-
+# frozen_string_literal: true
 
 $:.unshift '../../lib'
 require 'rack'
diff --git a/test/cgi/sample_rackup.ru b/test/cgi/sample_rackup.ru
index a73df81c..c8e94c9f 100755
--- a/test/cgi/sample_rackup.ru
+++ b/test/cgi/sample_rackup.ru
@@ -1,4 +1,4 @@
-# -*- ruby -*-
+# frozen_string_literal: true
 
 require '../testrequest'
 
diff --git a/test/cgi/test b/test/cgi/test
index e4837a4e..a1de2fbe 100755
--- a/test/cgi/test
+++ b/test/cgi/test
@@ -1,5 +1,5 @@
 #!/usr/bin/env ruby
-# -*- ruby -*-
+# frozen_string_literal: true
 
 $: << File.join(File.dirname(__FILE__), "..", "..", "lib")
 
diff --git a/test/cgi/test.fcgi b/test/cgi/test.fcgi
index 31f43399..a67e547a 100755
--- a/test/cgi/test.fcgi
+++ b/test/cgi/test.fcgi
@@ -1,5 +1,5 @@
 #!/usr/bin/env ruby
-# -*- ruby -*-
+# frozen_string_literal: true
 
 require 'uri'
 $:.unshift '../../lib'
diff --git a/test/cgi/test.gz b/test/cgi/test.gz
index 312c60e2..a23c856c 100644
--- a/test/cgi/test.gz
+++ b/test/cgi/test.gz
Binary files differ
diff --git a/test/cgi/test.ru b/test/cgi/test.ru
index 7913ef78..1263778d 100755
--- a/test/cgi/test.ru
+++ b/test/cgi/test.ru
@@ -1,5 +1,5 @@
 #!../../bin/rackup
-# -*- ruby -*-
+# frozen_string_literal: true
 
 require '../testrequest'
 run Rack::Lint.new(TestRequest.new)
diff --git a/test/gemloader.rb b/test/gemloader.rb
index 22be6975..f38c8036 100644
--- a/test/gemloader.rb
+++ b/test/gemloader.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rubygems'
 project = 'rack'
 gemspec = File.expand_path("#{project}.gemspec", Dir.pwd)
diff --git a/test/helper.rb b/test/helper.rb
index aa9c0e0a..dff89558 100644
--- a/test/helper.rb
+++ b/test/helper.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 
 module Rack
   class TestCase < Minitest::Test
diff --git a/test/multipart/filename_with_null_byte b/test/multipart/filename_with_null_byte
new file mode 100644
index 00000000..961d44c4
--- /dev/null
+++ b/test/multipart/filename_with_null_byte
@@ -0,0 +1,7 @@
+--AaB03x
+Content-Type: image/jpeg
+Content-Disposition: attachment; name="files"; filename="flowers.exe%00.jpg"
+Content-Description: a complete map of the human genome
+
+contents
+--AaB03x--
diff --git a/test/multipart/filename_with_plus b/test/multipart/filename_with_plus
new file mode 100644
index 00000000..aa75022b
--- /dev/null
+++ b/test/multipart/filename_with_plus
@@ -0,0 +1,6 @@
+--AaB03x
+Content-Disposition: form-data; name="files"; filename="foo+bar"
+Content-Type: application/octet-stream
+
+contents
+--AaB03x--
diff --git a/test/rackup/config.ru b/test/rackup/config.ru
index f1e2e1f3..fa9b6eca 100644
--- a/test/rackup/config.ru
+++ b/test/rackup/config.ru
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require "#{File.dirname(__FILE__)}/../testrequest"
 
 $stderr = File.open("#{File.dirname(__FILE__)}/log_output", "w")
diff --git a/test/registering_handler/rack/handler/registering_myself.rb b/test/registering_handler/rack/handler/registering_myself.rb
index 4964953b..21b60516 100644
--- a/test/registering_handler/rack/handler/registering_myself.rb
+++ b/test/registering_handler/rack/handler/registering_myself.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Rack
   module Handler
     class RegisteringMyself
diff --git a/test/spec_auth_basic.rb b/test/spec_auth_basic.rb
index 1e19bf66..3e479ace 100644
--- a/test/spec_auth_basic.rb
+++ b/test/spec_auth_basic.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/auth/basic'
 require 'rack/lint'
 require 'rack/mock'
@@ -10,7 +12,7 @@ describe Rack::Auth::Basic do
 
   def unprotected_app
     Rack::Lint.new lambda { |env|
-      [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ]
+      [ 200, { 'Content-Type' => 'text/plain' }, ["Hi #{env['REMOTE_USER']}"] ]
     }
   end
 
@@ -75,6 +77,13 @@ describe Rack::Auth::Basic do
     end
   end
 
+  it 'return 401 Bad Request for a nil authorization header' do
+    request 'HTTP_AUTHORIZATION' => nil do |response|
+      response.must_be :client_error?
+      response.status.must_equal 401
+    end
+  end
+
   it 'takes realm as optional constructor arg' do
     app = Rack::Auth::Basic.new(unprotected_app, realm) { true }
     realm.must_equal app.realm
diff --git a/test/spec_auth_digest.rb b/test/spec_auth_digest.rb
index 7230bb68..cc205aa9 100644
--- a/test/spec_auth_digest.rb
+++ b/test/spec_auth_digest.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/auth/digest/md5'
 require 'rack/lint'
 require 'rack/mock'
@@ -11,12 +13,12 @@ describe Rack::Auth::Digest::MD5 do
   def unprotected_app
     Rack::Lint.new lambda { |env|
       friend = Rack::Utils.parse_query(env["QUERY_STRING"])["friend"]
-      [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}#{friend ? " and #{friend}" : ''}"] ]
+      [ 200, { 'Content-Type' => 'text/plain' }, ["Hi #{env['REMOTE_USER']}#{friend ? " and #{friend}" : ''}"] ]
     }
   end
 
   def protected_app
-    Rack::Auth::Digest::MD5.new(unprotected_app, :realm => realm, :opaque => 'this-should-be-secret') do |username|
+    Rack::Auth::Digest::MD5.new(unprotected_app, realm: realm, opaque: 'this-should-be-secret') do |username|
       { 'Alice' => 'correct-password' }[username]
     end
   end
@@ -158,7 +160,7 @@ describe Rack::Auth::Digest::MD5 do
     begin
       Rack::Auth::Digest::Nonce.time_limit = 10
 
-      request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', :wait => 1 do |response|
+      request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', wait: 1 do |response|
         response.status.must_equal 200
         response.body.to_s.must_equal 'Hi Alice'
         response.headers['WWW-Authenticate'].wont_match(/\bstale=true\b/)
@@ -172,7 +174,7 @@ describe Rack::Auth::Digest::MD5 do
     begin
       Rack::Auth::Digest::Nonce.time_limit = 1
 
-      request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', :wait => 2 do |response|
+      request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', wait: 2 do |response|
         assert_digest_auth_challenge response
         response.headers['WWW-Authenticate'].must_match(/\bstale=true\b/)
       end
@@ -247,7 +249,7 @@ describe Rack::Auth::Digest::MD5 do
 
   it 'return application output if correct credentials given for PUT (using method override of POST)' do
     @request = Rack::MockRequest.new(protected_app_with_method_override)
-    request_with_digest_auth 'POST', '/', 'Alice', 'correct-password', :input => "_method=put" do |response|
+    request_with_digest_auth 'POST', '/', 'Alice', 'correct-password', input: "_method=put" do |response|
       response.status.must_equal 200
       response.body.to_s.must_equal 'Hi Alice'
     end
diff --git a/test/spec_body_proxy.rb b/test/spec_body_proxy.rb
index 4db447a0..6be79f8b 100644
--- a/test/spec_body_proxy.rb
+++ b/test/spec_body_proxy.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/body_proxy'
 require 'stringio'
 
@@ -40,7 +42,7 @@ describe Rack::BodyProxy do
     called = false
 
     begin
-      proxy  = Rack::BodyProxy.new(object) { called = true }
+      proxy = Rack::BodyProxy.new(object) { called = true }
       called.must_equal false
       proxy.close
     rescue RuntimeError => e
diff --git a/test/spec_builder.rb b/test/spec_builder.rb
index ae1c4006..853fb7b1 100644
--- a/test/spec_builder.rb
+++ b/test/spec_builder.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/builder'
 require 'rack/lint'
 require 'rack/mock'
@@ -31,10 +33,10 @@ describe Rack::Builder do
   it "supports mapping" do
     app = builder_to_app do
       map '/' do |outer_env|
-        run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] }
+        run lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, ['root']] }
       end
       map '/sub' do
-        run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] }
+        run lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, ['sub']] }
       end
     end
     Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'root'
@@ -47,7 +49,7 @@ describe Rack::Builder do
       map '/' do |outer_env|
         run lambda { |inner_env|
           inner_env['new_key'] = 'new_value'
-          [200, {"Content-Type" => "text/plain"}, ['root']]
+          [200, { "Content-Type" => "text/plain" }, ['root']]
         }
       end
     end
@@ -55,6 +57,19 @@ describe Rack::Builder do
     NothingMiddleware.env['new_key'].must_equal 'new_value'
   end
 
+  it "dupe #to_app when mapping so Rack::Reloader can reload the application on each request" do
+    app = builder do
+      map '/' do |outer_env|
+        run lambda { |env|  [200, { "Content-Type" => "text/plain" }, [object_id.to_s]] }
+      end
+    end
+
+    builder_app1_id = Rack::MockRequest.new(app).get("/").body.to_s
+    builder_app2_id = Rack::MockRequest.new(app).get("/").body.to_s
+
+    builder_app2_id.wont_equal builder_app1_id
+  end
+
   it "chains apps by default" do
     app = builder_to_app do
       use Rack::ShowExceptions
@@ -84,7 +99,7 @@ describe Rack::Builder do
         'secret' == password
       end
 
-      run lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hi Boss']] }
+      run lambda { |env| [200, { "Content-Type" => "text/plain" }, ['Hi Boss']] }
     end
 
     response = Rack::MockRequest.new(app).get("/")
@@ -112,9 +127,9 @@ describe Rack::Builder do
   it "can mix map and run for endpoints" do
     app = builder do
       map '/sub' do
-        run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] }
+        run lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, ['sub']] }
       end
-      run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] }
+      run lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, ['root']] }
     end
 
     Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'root'
@@ -151,7 +166,7 @@ describe Rack::Builder do
         def call(env)
           raise "bzzzt"  if @called > 0
         @called += 1
-          [200, {'Content-Type' => 'text/plain'}, ['OK']]
+          [200, { 'Content-Type' => 'text/plain' }, ['OK']]
         end
       end
 
@@ -174,6 +189,27 @@ describe Rack::Builder do
     Rack::MockRequest.new(app).get("/").must_be :server_error?
   end
 
+  it "supports #freeze_app for freezing app and middleware" do
+    app = builder do
+      freeze_app
+      use Rack::ShowExceptions
+      use(Class.new do
+        def initialize(app) @app = app end
+        def call(env) @a = 1 if env['PATH_INFO'] == '/a'; @app.call(env) end
+      end)
+      o = Object.new
+      def o.call(env)
+        @a = 1 if env['PATH_INFO'] == '/b';
+        [200, {}, []]
+      end
+      run o
+    end
+
+    Rack::MockRequest.new(app).get("/a").must_be :server_error?
+    Rack::MockRequest.new(app).get("/b").must_be :server_error?
+    Rack::MockRequest.new(app).get("/c").status.must_equal 200
+  end
+
   it 'complains about a missing run' do
     proc do
       Rack::Lint.new Rack::Builder.app { use Rack::ShowExceptions }
@@ -204,13 +240,6 @@ describe Rack::Builder do
       env.must_equal({})
     end
 
-    it "requires anything not ending in .ru" do
-      $: << File.dirname(__FILE__)
-      app, * = Rack::Builder.parse_file 'builder/anything'
-      Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'OK'
-      $:.pop
-    end
-
     it 'requires an_underscore_app not ending in .ru' do
       $: << File.dirname(__FILE__)
       app, * = Rack::Builder.parse_file 'builder/an_underscore_app'
@@ -220,7 +249,12 @@ describe Rack::Builder do
 
     it "sets __LINE__ correctly" do
       app, _ = Rack::Builder.parse_file config_file('line.ru')
-      Rack::MockRequest.new(app).get("/").body.to_s.must_equal '1'
+      Rack::MockRequest.new(app).get("/").body.to_s.must_equal '3'
+    end
+
+    it "strips leading unicode byte order mark when present" do
+      app, _ = Rack::Builder.parse_file config_file('bom.ru')
+      Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'OK'
     end
   end
 
diff --git a/test/spec_cascade.rb b/test/spec_cascade.rb
index 180ce46e..a06aefeb 100644
--- a/test/spec_cascade.rb
+++ b/test/spec_cascade.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack'
 require 'rack/cascade'
 require 'rack/file'
@@ -17,7 +19,7 @@ describe Rack::Cascade do
   app2 = Rack::URLMap.new("/crash" => lambda { |env| raise "boom" })
 
   app3 = Rack::URLMap.new("/foo" => lambda { |env|
-                            [200, { "Content-Type" => "text/plain"}, [""]]})
+                            [200, { "Content-Type" => "text/plain" }, [""]]})
 
   it "dispatch onward on 404 and 405 by default" do
     cascade = cascade([app1, app2, app3])
diff --git a/test/spec_cgi.rb b/test/spec_cgi.rb
index 77020c2f..4e8111a6 100644
--- a/test/spec_cgi.rb
+++ b/test/spec_cgi.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'helper'
 
 if defined? LIGHTTPD_PID
@@ -35,7 +37,7 @@ describe Rack::Handler::CGI do
 
   it "have rack headers" do
     GET("/test")
-    response["rack.version"].must_equal [1,3]
+    response["rack.version"].must_equal [1, 3]
     assert_equal false, response["rack.multithread"]
     assert_equal true, response["rack.multiprocess"]
     assert_equal true, response["rack.run_once"]
@@ -59,7 +61,7 @@ describe Rack::Handler::CGI do
   end
 
   it "have CGI headers on POST" do
-    POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
+    POST("/test", { "rack-form-data" => "23" }, { 'X-test-header' => '42' })
     status.must_equal 200
     response["REQUEST_METHOD"].must_equal "POST"
     response["SCRIPT_NAME"].must_equal "/test"
@@ -70,7 +72,7 @@ describe Rack::Handler::CGI do
   end
 
   it "support HTTP auth" do
-    GET("/test", {:user => "ruth", :passwd => "secret"})
+    GET("/test", { user: "ruth", passwd: "secret" })
     response["HTTP_AUTHORIZATION"].must_equal "Basic cnV0aDpzZWNyZXQ="
   end
 
diff --git a/test/spec_chunked.rb b/test/spec_chunked.rb
index 7bbcfd92..daa36cad 100644
--- a/test/spec_chunked.rb
+++ b/test/spec_chunked.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/chunked'
 require 'rack/lint'
 require 'rack/mock'
@@ -19,8 +21,28 @@ describe Rack::Chunked do
       env_for('/', 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => 'GET')
   end
 
+  class TrailerBody
+    def each(&block)
+      ['Hello', ' ', 'World!'].each(&block)
+    end
+
+    def trailers
+      { "Expires" => "tomorrow" }
+    end
+  end
+
+  it 'yields trailer headers after the response' do
+    app = lambda { |env|
+      [200, { "Content-Type" => "text/plain", "Trailer" => "Expires" }, TrailerBody.new]
+    }
+    response = Rack::MockResponse.new(*chunked(app).call(@env))
+    response.headers.wont_include 'Content-Length'
+    response.headers['Transfer-Encoding'].must_equal 'chunked'
+    response.body.must_equal "5\r\nHello\r\n1\r\n \r\n6\r\nWorld!\r\n0\r\nExpires: tomorrow\r\n\r\n"
+  end
+
   it 'chunk responses with no Content-Length' do
-    app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] }
+    app = lambda { |env| [200, { "Content-Type" => "text/plain" }, ['Hello', ' ', 'World!']] }
     response = Rack::MockResponse.new(*chunked(app).call(@env))
     response.headers.wont_include 'Content-Length'
     response.headers['Transfer-Encoding'].must_equal 'chunked'
@@ -28,7 +50,7 @@ describe Rack::Chunked do
   end
 
   it 'chunks empty bodies properly' do
-    app = lambda { |env| [200, {"Content-Type" => "text/plain"}, []] }
+    app = lambda { |env| [200, { "Content-Type" => "text/plain" }, []] }
     response = Rack::MockResponse.new(*chunked(app).call(@env))
     response.headers.wont_include 'Content-Length'
     response.headers['Transfer-Encoding'].must_equal 'chunked'
@@ -37,18 +59,18 @@ describe Rack::Chunked do
 
   it 'chunks encoded bodies properly' do
     body = ["\uFFFEHello", " ", "World"].map {|t| t.encode("UTF-16LE") }
-    app  = lambda { |env| [200, {"Content-Type" => "text/plain"}, body] }
+    app  = lambda { |env| [200, { "Content-Type" => "text/plain" }, body] }
     response = Rack::MockResponse.new(*chunked(app).call(@env))
     response.headers.wont_include 'Content-Length'
     response.headers['Transfer-Encoding'].must_equal 'chunked'
     response.body.encoding.to_s.must_equal "ASCII-8BIT"
-    response.body.must_equal "c\r\n\xFE\xFFH\x00e\x00l\x00l\x00o\x00\r\n2\r\n \x00\r\na\r\nW\x00o\x00r\x00l\x00d\x00\r\n0\r\n\r\n".force_encoding("BINARY")
-    response.body.must_equal "c\r\n\xFE\xFFH\x00e\x00l\x00l\x00o\x00\r\n2\r\n \x00\r\na\r\nW\x00o\x00r\x00l\x00d\x00\r\n0\r\n\r\n".force_encoding(Encoding::BINARY)
+    response.body.must_equal "c\r\n\xFE\xFFH\x00e\x00l\x00l\x00o\x00\r\n2\r\n \x00\r\na\r\nW\x00o\x00r\x00l\x00d\x00\r\n0\r\n\r\n".dup.force_encoding("BINARY")
+    response.body.must_equal "c\r\n\xFE\xFFH\x00e\x00l\x00l\x00o\x00\r\n2\r\n \x00\r\na\r\nW\x00o\x00r\x00l\x00d\x00\r\n0\r\n\r\n".dup.force_encoding(Encoding::BINARY)
   end
 
   it 'not modify response when Content-Length header present' do
     app = lambda { |env|
-      [200, {"Content-Type" => "text/plain", 'Content-Length'=>'12'}, ['Hello', ' ', 'World!']]
+      [200, { "Content-Type" => "text/plain", 'Content-Length' => '12' }, ['Hello', ' ', 'World!']]
     }
     status, headers, body = chunked(app).call(@env)
     status.must_equal 200
@@ -58,7 +80,7 @@ describe Rack::Chunked do
   end
 
   it 'not modify response when client is HTTP/1.0' do
-    app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] }
+    app = lambda { |env| [200, { "Content-Type" => "text/plain" }, ['Hello', ' ', 'World!']] }
     @env['HTTP_VERSION'] = 'HTTP/1.0'
     status, headers, body = chunked(app).call(@env)
     status.must_equal 200
@@ -67,7 +89,7 @@ describe Rack::Chunked do
   end
 
   it 'not modify response when client is ancient, pre-HTTP/1.0' do
-    app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] }
+    app = lambda { |env| [200, { "Content-Type" => "text/plain" }, ['Hello', ' ', 'World!']] }
     check = lambda do
       status, headers, body = chunked(app).call(@env.dup)
       status.must_equal 200
@@ -84,7 +106,7 @@ describe Rack::Chunked do
 
   it 'not modify response when Transfer-Encoding header already present' do
     app = lambda { |env|
-      [200, {"Content-Type" => "text/plain", 'Transfer-Encoding' => 'identity'}, ['Hello', ' ', 'World!']]
+      [200, { "Content-Type" => "text/plain", 'Transfer-Encoding' => 'identity' }, ['Hello', ' ', 'World!']]
     }
     status, headers, body = chunked(app).call(@env)
     status.must_equal 200
@@ -92,7 +114,7 @@ describe Rack::Chunked do
     body.join.must_equal 'Hello World!'
   end
 
-  [100, 204, 205, 304].each do |status_code|
+  [100, 204, 304].each do |status_code|
     it "not modify response when status code is #{status_code}" do
       app = lambda { |env| [status_code, {}, []] }
       status, headers, _ = chunked(app).call(@env)
diff --git a/test/spec_common_logger.rb b/test/spec_common_logger.rb
index 3589576b..330a6480 100644
--- a/test/spec_common_logger.rb
+++ b/test/spec_common_logger.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/common_logger'
 require 'rack/lint'
 require 'rack/mock'
@@ -11,15 +13,15 @@ describe Rack::CommonLogger do
 
   app = Rack::Lint.new lambda { |env|
     [200,
-     {"Content-Type" => "text/html", "Content-Length" => length.to_s},
+     { "Content-Type" => "text/html", "Content-Length" => length.to_s },
      [obj]]}
   app_without_length = Rack::Lint.new lambda { |env|
     [200,
-     {"Content-Type" => "text/html"},
+     { "Content-Type" => "text/html" },
      []]}
   app_with_zero_length = Rack::Lint.new lambda { |env|
     [200,
-     {"Content-Type" => "text/html", "Content-Length" => "0"},
+     { "Content-Type" => "text/html", "Content-Length" => "0" },
      []]}
 
   it "log to rack.errors by default" do
diff --git a/test/spec_conditional_get.rb b/test/spec_conditional_get.rb
index 58f37ad5..8402f04e 100644
--- a/test/spec_conditional_get.rb
+++ b/test/spec_conditional_get.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'time'
 require 'rack/conditional_get'
 require 'rack/mock'
@@ -11,7 +13,7 @@ describe Rack::ConditionalGet do
   it "set a 304 status and truncate body when If-Modified-Since hits" do
     timestamp = Time.now.httpdate
     app = conditional_get(lambda { |env|
-      [200, {'Last-Modified'=>timestamp}, ['TEST']] })
+      [200, { 'Last-Modified' => timestamp }, ['TEST']] })
 
     response = Rack::MockRequest.new(app).
       get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp)
@@ -22,7 +24,7 @@ describe Rack::ConditionalGet do
 
   it "set a 304 status and truncate body when If-Modified-Since hits and is higher than current time" do
     app = conditional_get(lambda { |env|
-      [200, {'Last-Modified'=>(Time.now - 3600).httpdate}, ['TEST']] })
+      [200, { 'Last-Modified' => (Time.now - 3600).httpdate }, ['TEST']] })
 
     response = Rack::MockRequest.new(app).
       get("/", 'HTTP_IF_MODIFIED_SINCE' => Time.now.httpdate)
@@ -33,7 +35,7 @@ describe Rack::ConditionalGet do
 
   it "set a 304 status and truncate body when If-None-Match hits" do
     app = conditional_get(lambda { |env|
-      [200, {'ETag'=>'1234'}, ['TEST']] })
+      [200, { 'ETag' => '1234' }, ['TEST']] })
 
     response = Rack::MockRequest.new(app).
       get("/", 'HTTP_IF_NONE_MATCH' => '1234')
@@ -45,7 +47,7 @@ describe Rack::ConditionalGet do
   it "not set a 304 status if If-Modified-Since hits but Etag does not" do
     timestamp = Time.now.httpdate
     app = conditional_get(lambda { |env|
-      [200, {'Last-Modified'=>timestamp, 'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] })
+      [200, { 'Last-Modified' => timestamp, 'Etag' => '1234', 'Content-Type' => 'text/plain' }, ['TEST']] })
 
     response = Rack::MockRequest.new(app).
       get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '4321')
@@ -57,7 +59,7 @@ describe Rack::ConditionalGet do
   it "set a 304 status and truncate body when both If-None-Match and If-Modified-Since hits" do
     timestamp = Time.now.httpdate
     app = conditional_get(lambda { |env|
-      [200, {'Last-Modified'=>timestamp, 'ETag'=>'1234'}, ['TEST']] })
+      [200, { 'Last-Modified' => timestamp, 'ETag' => '1234' }, ['TEST']] })
 
     response = Rack::MockRequest.new(app).
       get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '1234')
@@ -68,7 +70,7 @@ describe Rack::ConditionalGet do
 
   it "not affect non-GET/HEAD requests" do
     app = conditional_get(lambda { |env|
-      [200, {'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] })
+      [200, { 'Etag' => '1234', 'Content-Type' => 'text/plain' }, ['TEST']] })
 
     response = Rack::MockRequest.new(app).
       post("/", 'HTTP_IF_NONE_MATCH' => '1234')
@@ -79,7 +81,7 @@ describe Rack::ConditionalGet do
 
   it "not affect non-200 requests" do
     app = conditional_get(lambda { |env|
-      [302, {'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] })
+      [302, { 'Etag' => '1234', 'Content-Type' => 'text/plain' }, ['TEST']] })
 
     response = Rack::MockRequest.new(app).
       get("/", 'HTTP_IF_NONE_MATCH' => '1234')
@@ -91,7 +93,7 @@ describe Rack::ConditionalGet do
   it "not affect requests with malformed HTTP_IF_NONE_MATCH" do
     bad_timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S %z')
     app = conditional_get(lambda { |env|
-      [200,{'Last-Modified'=>(Time.now - 3600).httpdate, 'Content-Type' => 'text/plain'}, ['TEST']] })
+      [200, { 'Last-Modified' => (Time.now - 3600).httpdate, 'Content-Type' => 'text/plain' }, ['TEST']] })
 
     response = Rack::MockRequest.new(app).
       get("/", 'HTTP_IF_MODIFIED_SINCE' => bad_timestamp)
diff --git a/test/spec_config.rb b/test/spec_config.rb
index 16f0a664..d97107b6 100644
--- a/test/spec_config.rb
+++ b/test/spec_config.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/builder'
 require 'rack/config'
 require 'rack/content_length'
@@ -13,7 +15,7 @@ describe Rack::Config do
         env['greeting'] = 'hello'
       end
       run lambda { |env|
-        [200, {'Content-Type' => 'text/plain'}, [env['greeting'] || '']]
+        [200, { 'Content-Type' => 'text/plain' }, [env['greeting'] || '']]
       }
     end
 
diff --git a/test/spec_content_length.rb b/test/spec_content_length.rb
index 261e4505..2e7a8581 100644
--- a/test/spec_content_length.rb
+++ b/test/spec_content_length.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/content_length'
 require 'rack/lint'
 require 'rack/mock'
@@ -13,7 +15,7 @@ describe Rack::ContentLength do
   end
 
   it "set Content-Length on Array bodies if none is set" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["Hello, World!"]] }
     response = content_length(app).call(request)
     response[1]['Content-Length'].must_equal '13'
   end
@@ -22,13 +24,13 @@ describe Rack::ContentLength do
     body = lambda { "Hello World!" }
     def body.each ; yield call ; end
 
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, body] }
     response = content_length(app).call(request)
     response[1]['Content-Length'].must_be_nil
   end
 
   it "not change Content-Length if it is already set" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Content-Length' => '1'}, "Hello, World!"] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain', 'Content-Length' => '1' }, "Hello, World!"] }
     response = content_length(app).call(request)
     response[1]['Content-Length'].must_equal '1'
   end
@@ -36,13 +38,13 @@ describe Rack::ContentLength do
   it "not set Content-Length on 304 responses" do
     app = lambda { |env| [304, {}, []] }
     response = content_length(app).call(request)
-    response[1]['Content-Length'].must_equal nil
+    response[1]['Content-Length'].must_be_nil
   end
 
   it "not set Content-Length when Transfer-Encoding is chunked" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Transfer-Encoding' => 'chunked'}, []] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain', 'Transfer-Encoding' => 'chunked' }, []] }
     response = content_length(app).call(request)
-    response[1]['Content-Length'].must_equal nil
+    response[1]['Content-Length'].must_be_nil
   end
 
   # Using "Connection: close" for this is fairly contended. It might be useful
@@ -51,7 +53,7 @@ describe Rack::ContentLength do
   # should "not force a Content-Length when Connection:close" do
   #   app = lambda { |env| [200, {'Connection' => 'close'}, []] }
   #   response = content_length(app).call({})
-  #   response[1]['Content-Length'].must_equal nil
+  #   response[1]['Content-Length'].must_be_nil
   # end
 
   it "close bodies that need to be closed" do
@@ -62,9 +64,9 @@ describe Rack::ContentLength do
       def to_ary; end
     end.new(%w[one two three])
 
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, body] }
     response = content_length(app).call(request)
-    body.closed.must_equal nil
+    body.closed.must_be_nil
     response[2].close
     body.closed.must_equal true
   end
@@ -77,7 +79,7 @@ describe Rack::ContentLength do
       def to_ary; end
     end.new(%w[one two three])
 
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, body] }
     response = content_length(app).call(request)
     expected = %w[one two three]
     response[1]['Content-Length'].must_equal expected.join.size.to_s
diff --git a/test/spec_content_type.rb b/test/spec_content_type.rb
index 281879f3..53f1d172 100644
--- a/test/spec_content_type.rb
+++ b/test/spec_content_type.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/content_type'
 require 'rack/lint'
 require 'rack/mock'
@@ -26,21 +28,31 @@ describe Rack::ContentType do
   end
 
   it "not change Content-Type if it is already set" do
-    app = lambda { |env| [200, {'Content-Type' => 'foo/bar'}, "Hello, World!"] }
+    app = lambda { |env| [200, { 'Content-Type' => 'foo/bar' }, "Hello, World!"] }
     headers = content_type(app).call(request)[1]
     headers['Content-Type'].must_equal 'foo/bar'
   end
 
   it "detect Content-Type case insensitive" do
-    app = lambda { |env| [200, {'CONTENT-Type' => 'foo/bar'}, "Hello, World!"] }
+    app = lambda { |env| [200, { 'CONTENT-Type' => 'foo/bar' }, "Hello, World!"] }
     headers = content_type(app).call(request)[1]
-    headers.to_a.select { |k,v| k.downcase == "content-type" }.
-      must_equal [["CONTENT-Type","foo/bar"]]
+    headers.to_a.select { |k, v| k.downcase == "content-type" }.
+      must_equal [["CONTENT-Type", "foo/bar"]]
+  end
+
+  [100, 204, 304].each do |code|
+    it "not set Content-Type on #{code} responses" do
+      app = lambda { |env| [code, {}, []] }
+      response = content_type(app, "text/html").call(request)
+      response[1]['Content-Type'].must_be_nil
+    end
   end
 
-  it "not set Content-Type on 304 responses" do
-    app = lambda { |env| [304, {}, []] }
-    response = content_type(app, "text/html").call(request)
-    response[1]['Content-Type'].must_equal nil
+  ['100', '204', '304'].each do |code|
+    it "not set Content-Type on #{code} responses if status is a string" do
+      app = lambda { |env| [code, {}, []] }
+      response = content_type(app, "text/html").call(request)
+      response[1]['Content-Type'].must_be_nil
+    end
   end
 end
diff --git a/test/spec_deflater.rb b/test/spec_deflater.rb
index ba7ec5d3..75244dcc 100644
--- a/test/spec_deflater.rb
+++ b/test/spec_deflater.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'stringio'
 require 'time'  # for Time#httpdate
 require 'rack/deflater'
@@ -44,6 +46,8 @@ describe Rack::Deflater do
       [accept_encoding, accept_encoding.dup]
     end
 
+    start = Time.now.to_i
+
     # build response
     status, headers, body = build_response(
       options['app_status'] || expected_status,
@@ -57,7 +61,7 @@ describe Rack::Deflater do
 
     # verify body
     unless options['skip_body_verify']
-      body_text = ''
+      body_text = ''.dup
       body.each { |part| body_text << part }
 
       deflated_body = case expected_encoding
@@ -67,6 +71,13 @@ describe Rack::Deflater do
       when 'gzip'
         io = StringIO.new(body_text)
         gz = Zlib::GzipReader.new(io)
+        mtime = gz.mtime.to_i
+        if last_mod = headers['Last-Modified']
+          Time.httpdate(last_mod).to_i.must_equal mtime
+        else
+          mtime.must_be(:<=, Time.now.to_i)
+          mtime.must_be(:>=, start.to_i)
+        end
         tmp = gz.read
         gz.close
         tmp
@@ -81,13 +92,35 @@ describe Rack::Deflater do
     yield(status, headers, body) if block_given?
   end
 
+  # automatic gzip detection (streamable)
+  def auto_inflater
+    Zlib::Inflate.new(32 + Zlib::MAX_WBITS)
+  end
+
+  def deflate_or_gzip
+    { 'deflate, gzip' => 'gzip' }
+  end
+
   it 'be able to deflate bodies that respond to each' do
     app_body = Object.new
     class << app_body; def each; yield('foo'); yield('bar'); end; end
 
-    verify(200, 'foobar', 'deflate', { 'app_body' => app_body }) do |status, headers, body|
+    verify(200, 'foobar', deflate_or_gzip, { 'app_body' => app_body }) do |status, headers, body|
+      headers.must_equal({
+        'Content-Encoding' => 'gzip',
+        'Vary' => 'Accept-Encoding',
+        'Content-Type' => 'text/plain'
+      })
+    end
+  end
+
+  it 'be able to deflate bodies that respond to each and contain empty chunks' do
+    app_body = Object.new
+    class << app_body; def each; yield('foo'); yield(''); yield('bar'); end; end
+
+    verify(200, 'foobar', deflate_or_gzip, { 'app_body' => app_body }) do |status, headers, body|
       headers.must_equal({
-        'Content-Encoding' => 'deflate',
+        'Content-Encoding' => 'gzip',
         'Vary' => 'Accept-Encoding',
         'Content-Type' => 'text/plain'
       })
@@ -98,15 +131,15 @@ describe Rack::Deflater do
     app_body = Object.new
     class << app_body; def each; yield('foo'); yield('bar'); end; end
 
-    verify(200, app_body, 'deflate', { 'skip_body_verify' => true }) do |status, headers, body|
+    verify(200, app_body, deflate_or_gzip, { 'skip_body_verify' => true }) do |status, headers, body|
       headers.must_equal({
-        'Content-Encoding' => 'deflate',
+        'Content-Encoding' => 'gzip',
         'Vary' => 'Accept-Encoding',
         'Content-Type' => 'text/plain'
       })
 
       buf = []
-      inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
+      inflater = auto_inflater
       body.each { |part| buf << inflater.inflate(part) }
       buf << inflater.finish
 
@@ -118,32 +151,33 @@ describe Rack::Deflater do
     app_body = Object.new
     class << app_body; def each; yield('foo'); yield('bar'); end; end
     opts = { 'skip_body_verify' => true }
-    verify(200, app_body, 'deflate', opts) do |status, headers, body|
+    verify(200, app_body, 'gzip', opts) do |status, headers, body|
       headers.must_equal({
-        'Content-Encoding' => 'deflate',
+        'Content-Encoding' => 'gzip',
         'Vary' => 'Accept-Encoding',
         'Content-Type' => 'text/plain'
       })
 
       buf = []
-      inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
+      inflater = auto_inflater
       FakeDisconnect = Class.new(RuntimeError)
       assert_raises(FakeDisconnect, "not Zlib::DataError not raised") do
         body.each do |part|
-          buf << inflater.inflate(part)
+          tmp = inflater.inflate(part)
+          buf << tmp if tmp.bytesize > 0
           raise FakeDisconnect
         end
       end
-      assert_raises(Zlib::BufError) { inflater.finish }
+      inflater.finish
       buf.must_equal(%w(foo))
     end
   end
 
   # TODO: This is really just a special case of the above...
   it 'be able to deflate String bodies' do
-    verify(200, 'Hello world!', 'deflate') do |status, headers, body|
+    verify(200, 'Hello world!', deflate_or_gzip) do |status, headers, body|
       headers.must_equal({
-        'Content-Encoding' => 'deflate',
+        'Content-Encoding' => 'gzip',
         'Vary' => 'Accept-Encoding',
         'Content-Type' => 'text/plain'
       })
@@ -280,7 +314,7 @@ describe Rack::Deflater do
         'Content-Encoding' => 'identity'
       }
     }
-    verify(200, 'Hello World!', 'deflate', options)
+    verify(200, 'Hello World!', deflate_or_gzip, options)
   end
 
   it "deflate if content-type matches :include" do
@@ -289,7 +323,7 @@ describe Rack::Deflater do
         'Content-Type' => 'text/plain'
       },
       'deflater_options' => {
-        :include => %w(text/plain)
+        include: %w(text/plain)
       }
     }
     verify(200, 'Hello World!', 'gzip', options)
@@ -301,7 +335,7 @@ describe Rack::Deflater do
         'Content-Type' => 'text/plain; charset=us-ascii'
       },
       'deflater_options' => {
-        :include => %w(text/plain)
+        include: %w(text/plain)
       }
     }
     verify(200, 'Hello World!', 'gzip', options)
@@ -310,7 +344,7 @@ describe Rack::Deflater do
   it "not deflate if content-type is not set but given in :include" do
     options = {
       'deflater_options' => {
-        :include => %w(text/plain)
+        include: %w(text/plain)
       }
     }
     verify(304, 'Hello World!', { 'gzip' => nil }, options)
@@ -322,7 +356,7 @@ describe Rack::Deflater do
         'Content-Type' => 'text/plain'
       },
       'deflater_options' => {
-        :include => %w(text/json)
+        include: %w(text/json)
       }
     }
     verify(200, 'Hello World!', { 'gzip' => nil }, options)
@@ -331,16 +365,16 @@ describe Rack::Deflater do
   it "deflate response if :if lambda evaluates to true" do
     options = {
       'deflater_options' => {
-        :if => lambda { |env, status, headers, body| true }
+        if: lambda { |env, status, headers, body| true }
       }
     }
-    verify(200, 'Hello World!', 'deflate', options)
+    verify(200, 'Hello World!', deflate_or_gzip, options)
   end
 
   it "not deflate if :if lambda evaluates to false" do
     options = {
       'deflater_options' => {
-        :if => lambda { |env, status, headers, body| false }
+        if: lambda { |env, status, headers, body| false }
       }
     }
     verify(200, 'Hello World!', { 'gzip' => nil }, options)
@@ -354,7 +388,7 @@ describe Rack::Deflater do
         'Content-Length' => response_len.to_s
       },
       'deflater_options' => {
-        :if => lambda { |env, status, headers, body|
+        if: lambda { |env, status, headers, body|
           headers['Content-Length'].to_i >= response_len
         }
       }
@@ -362,4 +396,38 @@ describe Rack::Deflater do
 
     verify(200, response, 'gzip', options)
   end
+
+  it 'will honor sync: false to avoid unnecessary flushing' do
+    app_body = Object.new
+    class << app_body
+      def each
+        (0..20).each { |i| yield "hello\n" }
+      end
+    end
+
+    options = {
+      'deflater_options' => { sync: false },
+      'app_body' => app_body,
+      'skip_body_verify' => true,
+    }
+    verify(200, app_body, deflate_or_gzip, options) do |status, headers, body|
+      headers.must_equal({
+        'Content-Encoding' => 'gzip',
+        'Vary' => 'Accept-Encoding',
+        'Content-Type' => 'text/plain'
+      })
+
+      buf = ''.dup
+      raw_bytes = 0
+      inflater = auto_inflater
+      body.each do |part|
+        raw_bytes += part.bytesize
+        buf << inflater.inflate(part)
+      end
+      buf << inflater.finish
+      expect = "hello\n" * 21
+      buf.must_equal expect
+      raw_bytes.must_be(:<, expect.bytesize)
+    end
+  end
 end
diff --git a/test/spec_directory.rb b/test/spec_directory.rb
index 42bdea9f..8635ec90 100644
--- a/test/spec_directory.rb
+++ b/test/spec_directory.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/directory'
 require 'rack/lint'
 require 'rack/mock'
@@ -7,7 +9,7 @@ require 'fileutils'
 
 describe Rack::Directory do
   DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT
-  FILE_CATCH = proc{|env| [200, {'Content-Type'=>'text/plain', "Content-Length" => "7"}, ['passed!']] }
+  FILE_CATCH = proc{|env| [200, { 'Content-Type' => 'text/plain', "Content-Length" => "7" }, ['passed!']] }
 
   attr_reader :app
 
@@ -23,11 +25,11 @@ describe Rack::Directory do
       FileUtils.touch File.join(full_dir, "omg.txt")
       app = Rack::Directory.new(dir, FILE_CATCH)
       env = Rack::MockRequest.env_for("/#{plus_dir}/")
-      status,_,body = app.call env
+      status, _, body = app.call env
 
       assert_equal 200, status
 
-      str = ''
+      str = ''.dup
       body.each { |x| str << x }
       assert_match "foo+bar", str
     end
@@ -109,11 +111,11 @@ describe Rack::Directory do
       FileUtils.touch File.join(full_dir, "omg omg.txt")
       app = Rack::Directory.new(dir, FILE_CATCH)
       env = Rack::MockRequest.env_for(Rack::Utils.escape_path("/#{space_dir}/"))
-      status,_,body = app.call env
+      status, _, body = app.call env
 
       assert_equal 200, status
 
-      str = ''
+      str = ''.dup
       body.each { |x| str << x }
       assert_match "/foo%20bar/omg%20omg.txt", str
     end
diff --git a/test/spec_etag.rb b/test/spec_etag.rb
index 10ee2bd0..750ceaac 100644
--- a/test/spec_etag.rb
+++ b/test/spec_etag.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/etag'
 require 'rack/lint'
 require 'rack/mock'
@@ -20,79 +22,79 @@ describe Rack::ETag do
   end
 
   it "set ETag if none is set if status is 200" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["Hello, World!"]] }
     response = etag(app).call(request)
     response[1]['ETag'].must_equal "W/\"dffd6021bb2bd5b0af676290809ec3a5\""
   end
 
   it "set ETag if none is set if status is 201" do
-    app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
+    app = lambda { |env| [201, { 'Content-Type' => 'text/plain' }, ["Hello, World!"]] }
     response = etag(app).call(request)
     response[1]['ETag'].must_equal "W/\"dffd6021bb2bd5b0af676290809ec3a5\""
   end
 
   it "set Cache-Control to 'max-age=0, private, must-revalidate' (default) if none is set" do
-    app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
+    app = lambda { |env| [201, { 'Content-Type' => 'text/plain' }, ["Hello, World!"]] }
     response = etag(app).call(request)
     response[1]['Cache-Control'].must_equal 'max-age=0, private, must-revalidate'
   end
 
   it "set Cache-Control to chosen one if none is set" do
-    app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
+    app = lambda { |env| [201, { 'Content-Type' => 'text/plain' }, ["Hello, World!"]] }
     response = etag(app, nil, 'public').call(request)
     response[1]['Cache-Control'].must_equal 'public'
   end
 
   it "set a given Cache-Control even if digest could not be calculated" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, []] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
     response = etag(app, 'no-cache').call(request)
     response[1]['Cache-Control'].must_equal 'no-cache'
   end
 
   it "not set Cache-Control if it is already set" do
-    app = lambda { |env| [201, {'Content-Type' => 'text/plain', 'Cache-Control' => 'public'}, ["Hello, World!"]] }
+    app = lambda { |env| [201, { 'Content-Type' => 'text/plain', 'Cache-Control' => 'public' }, ["Hello, World!"]] }
     response = etag(app).call(request)
     response[1]['Cache-Control'].must_equal 'public'
   end
 
   it "not set Cache-Control if directive isn't present" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["Hello, World!"]] }
     response = etag(app, nil, nil).call(request)
-    response[1]['Cache-Control'].must_equal nil
+    response[1]['Cache-Control'].must_be_nil
   end
 
   it "not change ETag if it is already set" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'ETag' => '"abc"'}, ["Hello, World!"]] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain', 'ETag' => '"abc"' }, ["Hello, World!"]] }
     response = etag(app).call(request)
     response[1]['ETag'].must_equal "\"abc\""
   end
 
   it "not set ETag if body is empty" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Last-Modified' => Time.now.httpdate}, []] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain', 'Last-Modified' => Time.now.httpdate }, []] }
     response = etag(app).call(request)
     response[1]['ETag'].must_be_nil
   end
 
   it "not set ETag if Last-Modified is set" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Last-Modified' => Time.now.httpdate}, ["Hello, World!"]] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain', 'Last-Modified' => Time.now.httpdate }, ["Hello, World!"]] }
     response = etag(app).call(request)
     response[1]['ETag'].must_be_nil
   end
 
   it "not set ETag if a sendfile_body is given" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, sendfile_body] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, sendfile_body] }
     response = etag(app).call(request)
     response[1]['ETag'].must_be_nil
   end
 
   it "not set ETag if a status is not 200 or 201" do
-    app = lambda { |env| [401, {'Content-Type' => 'text/plain'}, ['Access denied.']] }
+    app = lambda { |env| [401, { 'Content-Type' => 'text/plain' }, ['Access denied.']] }
     response = etag(app).call(request)
     response[1]['ETag'].must_be_nil
   end
 
   it "not set ETag if no-cache is given" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Cache-Control' => 'no-cache, must-revalidate'}, ['Hello, World!']] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain', 'Cache-Control' => 'no-cache, must-revalidate' }, ['Hello, World!']] }
     response = etag(app).call(request)
     response[1]['ETag'].must_be_nil
   end
diff --git a/test/spec_events.rb b/test/spec_events.rb
index 7fc7b055..8c079361 100644
--- a/test/spec_events.rb
+++ b/test/spec_events.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'helper'
 require 'rack/events'
 
diff --git a/test/spec_fastcgi.rb b/test/spec_fastcgi.rb
index 5a48327b..db662a57 100644
--- a/test/spec_fastcgi.rb
+++ b/test/spec_fastcgi.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'helper'
 
 if defined? LIGHTTPD_PID
@@ -36,7 +38,7 @@ describe Rack::Handler::FastCGI do
 
   it "have rack headers" do
     GET("/test.fcgi")
-    response["rack.version"].must_equal [1,3]
+    response["rack.version"].must_equal [1, 3]
     assert_equal false, response["rack.multithread"]
     assert_equal true, response["rack.multiprocess"]
     assert_equal false, response["rack.run_once"]
@@ -60,7 +62,7 @@ describe Rack::Handler::FastCGI do
   end
 
   it "have CGI headers on POST" do
-    POST("/test.fcgi", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
+    POST("/test.fcgi", { "rack-form-data" => "23" }, { 'X-test-header' => '42' })
     status.must_equal 200
     response["REQUEST_METHOD"].must_equal "POST"
     response["SCRIPT_NAME"].must_equal "/test.fcgi"
@@ -71,7 +73,7 @@ describe Rack::Handler::FastCGI do
   end
 
   it "support HTTP auth" do
-    GET("/test.fcgi", {:user => "ruth", :passwd => "secret"})
+    GET("/test.fcgi", { user: "ruth", passwd: "secret" })
     response["HTTP_AUTHORIZATION"].must_equal "Basic cnV0aDpzZWNyZXQ="
   end
 
diff --git a/test/spec_file.rb b/test/spec_file.rb
index 3106e629..ba713c61 100644
--- a/test/spec_file.rb
+++ b/test/spec_file.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/file'
 require 'rack/lint'
 require 'rack/mock'
@@ -15,11 +17,11 @@ describe Rack::File do
       File.write File.join(dir, "you+me.txt"), "hello world"
       app = file(dir)
       env = Rack::MockRequest.env_for("/you+me.txt")
-      status,_,body = app.call env
+      status, _, body = app.call env
 
       assert_equal 200, status
 
-      str = ''
+      str = ''.dup
       body.each { |x| str << x }
       assert_match "hello world", str
     end
@@ -156,8 +158,8 @@ describe Rack::File do
 
     res.status.must_equal 206
     res["Content-Length"].must_equal "12"
-    res["Content-Range"].must_equal "bytes 22-33/193"
-    res.body.must_equal "-*- ruby -*-"
+    res["Content-Range"].must_equal "bytes 22-33/208"
+    res.body.must_equal "frozen_strin"
   end
 
   it "return error for unsatisfiable byte range" do
@@ -166,7 +168,7 @@ describe Rack::File do
     res = Rack::MockResponse.new(*file(DOCROOT).call(env))
 
     res.status.must_equal 416
-    res["Content-Range"].must_equal "bytes */193"
+    res["Content-Range"].must_equal "bytes */208"
   end
 
   it "support custom http headers" do
@@ -184,8 +186,8 @@ describe Rack::File do
     status, heads, _ = file(DOCROOT).call(env)
 
     status.must_equal 200
-    heads['Cache-Control'].must_equal nil
-    heads['Access-Control-Allow-Origin'].must_equal nil
+    heads['Cache-Control'].must_be_nil
+    heads['Access-Control-Allow-Origin'].must_be_nil
   end
 
   it "only support GET, HEAD, and OPTIONS requests" do
@@ -218,7 +220,7 @@ describe Rack::File do
     req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
     res = req.head "/cgi/test"
     res.must_be :successful?
-    res['Content-Length'].must_equal "193"
+    res['Content-Length'].must_equal "208"
   end
 
   it "default to a mime type of text/plain" do
@@ -239,7 +241,7 @@ describe Rack::File do
     req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT, nil, nil)))
     res = req.get "/cgi/test"
     res.must_be :successful?
-    res['Content-Type'].must_equal nil
+    res['Content-Type'].must_be_nil
   end
 
   it "return error when file not found for head request" do
@@ -248,4 +250,17 @@ describe Rack::File do
     res.body.must_be :empty?
   end
 
+  class MyFile < Rack::File
+    def response_body
+      "hello world"
+    end
+  end
+
+  it "behaves gracefully if response_body is present" do
+    file = Rack::Lint.new MyFile.new(DOCROOT)
+    res  = Rack::MockRequest.new(file).get("/cgi/test")
+
+    res.must_be :ok?
+  end
+
 end
diff --git a/test/spec_handler.rb b/test/spec_handler.rb
index dff474c9..5746dc22 100644
--- a/test/spec_handler.rb
+++ b/test/spec_handler.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/handler'
 
 class Rack::Handler::Lobster; end
diff --git a/test/spec_head.rb b/test/spec_head.rb
index 17b4a349..f6f41a5d 100644
--- a/test/spec_head.rb
+++ b/test/spec_head.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/head'
 require 'rack/lint'
 require 'rack/mock'
@@ -8,7 +10,7 @@ describe Rack::Head do
   def test_response(headers = {})
     body = StringIO.new "foo"
     app = lambda do |env|
-      [200, {"Content-type" => "test/plain", "Content-length" => "3"}, body]
+      [200, { "Content-type" => "test/plain", "Content-length" => "3" }, body]
     end
     request = Rack::MockRequest.env_for("/", headers)
     response = Rack::Lint.new(Rack::Head.new(app)).call(request)
diff --git a/test/spec_lint.rb b/test/spec_lint.rb
index 6d1c2c45..192f260f 100644
--- a/test/spec_lint.rb
+++ b/test/spec_lint.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'stringio'
 require 'tempfile'
 require 'rack/lint'
@@ -11,7 +13,7 @@ describe Rack::Lint do
 
   it "pass valid request" do
     Rack::Lint.new(lambda { |env|
-                     [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
+                     [200, { "Content-type" => "test/plain", "Content-length" => "3" }, ["foo"]]
                    }).call(env({})).first.must_equal 200
   end
 
@@ -187,14 +189,14 @@ describe Rack::Lint do
 
     lambda {
       Rack::Lint.new(lambda { |env|
-                       [200, {true=>false}, []]
+                       [200, { true => false }, []]
                      }).call(env({}))
     }.must_raise(Rack::Lint::LintError).
       message.must_equal "header key must be a string, was TrueClass"
 
     lambda {
       Rack::Lint.new(lambda { |env|
-                       [200, {"Status" => "404"}, []]
+                       [200, { "Status" => "404" }, []]
                      }).call(env({}))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/must not contain Status/)
@@ -216,7 +218,7 @@ describe Rack::Lint do
     invalid_headers.each do |invalid_header|
       lambda {
         Rack::Lint.new(lambda { |env|
-          [200, {invalid_header => "text/plain"}, []]
+          [200, { invalid_header => "text/plain" }, []]
         }).call(env({}))
       }.must_raise(Rack::Lint::LintError, "on invalid header: #{invalid_header}").
       message.must_equal("invalid header name: #{invalid_header}")
@@ -224,20 +226,20 @@ describe Rack::Lint do
     valid_headers = 0.upto(127).map(&:chr) - invalid_headers
     valid_headers.each do |valid_header|
       Rack::Lint.new(lambda { |env|
-                       [200, {valid_header => "text/plain"}, []]
+                       [200, { valid_header => "text/plain" }, []]
                      }).call(env({})).first.must_equal 200
     end
 
     lambda {
       Rack::Lint.new(lambda { |env|
-                       [200, {"Foo" => Object.new}, []]
+                       [200, { "Foo" => Object.new }, []]
                      }).call(env({}))
     }.must_raise(Rack::Lint::LintError).
       message.must_equal "a header value must be a String, but the value of 'Foo' is a Object"
 
     lambda {
       Rack::Lint.new(lambda { |env|
-                       [200, {"Foo" => [1, 2, 3]}, []]
+                       [200, { "Foo" => [1, 2, 3] }, []]
                      }).call(env({}))
     }.must_raise(Rack::Lint::LintError).
       message.must_equal "a header value must be a String, but the value of 'Foo' is a Array"
@@ -245,14 +247,14 @@ describe Rack::Lint do
 
     lambda {
       Rack::Lint.new(lambda { |env|
-                       [200, {"Foo-Bar" => "text\000plain"}, []]
+                       [200, { "Foo-Bar" => "text\000plain" }, []]
                      }).call(env({}))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/invalid header/)
 
     # line ends (010).must_be :allowed in header values.?
     Rack::Lint.new(lambda { |env|
-                     [200, {"Foo-Bar" => "one\ntwo\nthree", "Content-Length" => "0", "Content-Type" => "text/plain" }, []]
+                     [200, { "Foo-Bar" => "one\ntwo\nthree", "Content-Length" => "0", "Content-Type" => "text/plain" }, []]
                    }).call(env({})).first.must_equal 200
 
     # non-Hash header responses.must_be :allowed?
@@ -269,10 +271,10 @@ describe Rack::Lint do
     # }.must_raise(Rack::Lint::LintError).
     #   message.must_match(/No Content-Type/)
 
-    [100, 101, 204, 205, 304].each do |status|
+    [100, 101, 204, 304].each do |status|
       lambda {
         Rack::Lint.new(lambda { |env|
-                         [status, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
+                         [status, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
                        }).call(env({}))
       }.must_raise(Rack::Lint::LintError).
         message.must_match(/Content-Type header found/)
@@ -280,10 +282,10 @@ describe Rack::Lint do
   end
 
   it "notice content-length errors" do
-    [100, 101, 204, 205, 304].each do |status|
+    [100, 101, 204, 304].each do |status|
       lambda {
         Rack::Lint.new(lambda { |env|
-                         [status, {"Content-length" => "0"}, []]
+                         [status, { "Content-length" => "0" }, []]
                        }).call(env({}))
       }.must_raise(Rack::Lint::LintError).
         message.must_match(/Content-Length header found/)
@@ -291,7 +293,7 @@ describe Rack::Lint do
 
     lambda {
       Rack::Lint.new(lambda { |env|
-                       [200, {"Content-type" => "text/plain", "Content-Length" => "1"}, []]
+                       [200, { "Content-type" => "text/plain", "Content-Length" => "1" }, []]
                      }).call(env({}))[2].each { }
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/Content-Length header was 1, but should be 0/)
@@ -300,7 +302,7 @@ describe Rack::Lint do
   it "notice body errors" do
     lambda {
       body = Rack::Lint.new(lambda { |env|
-                               [200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]]
+                               [200, { "Content-type" => "text/plain", "Content-length" => "3" }, [1, 2, 3]]
                              }).call(env({}))[2]
       body.each { |part| }
     }.must_raise(Rack::Lint::LintError).
@@ -311,7 +313,7 @@ describe Rack::Lint do
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].gets("\r\n")
-                       [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
+                       [201, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
                      }).call(env({}))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/gets called with arguments/)
@@ -319,7 +321,7 @@ describe Rack::Lint do
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].read(1, 2, 3)
-                       [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
+                       [201, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
                      }).call(env({}))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/read called with too many arguments/)
@@ -327,7 +329,7 @@ describe Rack::Lint do
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].read("foo")
-                       [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
+                       [201, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
                      }).call(env({}))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/read called with non-integer and non-nil length/)
@@ -335,7 +337,7 @@ describe Rack::Lint do
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].read(-1)
-                       [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
+                       [201, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
                      }).call(env({}))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/read called with a negative length/)
@@ -343,7 +345,7 @@ describe Rack::Lint do
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].read(nil, nil)
-                       [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
+                       [201, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
                      }).call(env({}))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/read called with non-String buffer/)
@@ -351,7 +353,7 @@ describe Rack::Lint do
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].read(nil, 1)
-                       [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
+                       [201, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
                      }).call(env({}))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/read called with non-String buffer/)
@@ -359,7 +361,7 @@ describe Rack::Lint do
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].rewind(0)
-                       [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
+                       [201, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
                      }).call(env({}))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/rewind called with arguments/)
@@ -404,7 +406,7 @@ describe Rack::Lint do
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].gets
-                       [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
+                       [201, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
                      }).call(env("rack.input" => weirdio))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/gets didn't return a String/)
@@ -412,7 +414,7 @@ describe Rack::Lint do
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].each { |x| }
-                       [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
+                       [201, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
                      }).call(env("rack.input" => weirdio))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/each didn't yield a String/)
@@ -420,7 +422,7 @@ describe Rack::Lint do
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].read
-                       [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
+                       [201, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
                      }).call(env("rack.input" => weirdio))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/read didn't return nil or a String/)
@@ -428,7 +430,7 @@ describe Rack::Lint do
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].read
-                       [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
+                       [201, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
                      }).call(env("rack.input" => eof_weirdio))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/read\(nil\) returned nil on EOF/)
@@ -436,7 +438,7 @@ describe Rack::Lint do
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].rewind
-                       [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
+                       [201, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
                      }).call(env("rack.input" => weirdio))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/rewind raised Errno::ESPIPE/)
@@ -445,7 +447,7 @@ describe Rack::Lint do
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.input"].close
-                       [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
+                       [201, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
                      }).call(env({}))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/close must not be called/)
@@ -455,7 +457,7 @@ describe Rack::Lint do
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.errors"].write(42)
-                       [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
+                       [201, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
                      }).call(env({}))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/write not called with a String/)
@@ -463,7 +465,7 @@ describe Rack::Lint do
     lambda {
       Rack::Lint.new(lambda { |env|
                        env["rack.errors"].close
-                       [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
+                       [201, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
                      }).call(env({}))
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/close must not be called/)
@@ -471,25 +473,25 @@ describe Rack::Lint do
 
   it "notice HEAD errors" do
     Rack::Lint.new(lambda { |env|
-                     [200, {"Content-type" => "test/plain", "Content-length" => "3"}, []]
-                   }).call(env({"REQUEST_METHOD" => "HEAD"})).first.must_equal 200
+                     [200, { "Content-type" => "test/plain", "Content-length" => "3" }, []]
+                   }).call(env({ "REQUEST_METHOD" => "HEAD" })).first.must_equal 200
 
     lambda {
       Rack::Lint.new(lambda { |env|
-                       [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
-                     }).call(env({"REQUEST_METHOD" => "HEAD"}))[2].each { }
+                       [200, { "Content-type" => "test/plain", "Content-length" => "3" }, ["foo"]]
+                     }).call(env({ "REQUEST_METHOD" => "HEAD" }))[2].each { }
     }.must_raise(Rack::Lint::LintError).
       message.must_match(/body was given for HEAD/)
   end
 
   def assert_lint(*args)
-    hello_str = "hello world"
+    hello_str = "hello world".dup
     hello_str.force_encoding(Encoding::ASCII_8BIT)
 
     Rack::Lint.new(lambda { |env|
                      env["rack.input"].send(:read, *args)
-                     [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
-                   }).call(env({"rack.input" => StringIO.new(hello_str)})).
+                     [201, { "Content-type" => "text/plain", "Content-length" => "0" }, []]
+                   }).call(env({ "rack.input" => StringIO.new(hello_str) })).
       first.must_equal 201
   end
 
@@ -498,8 +500,8 @@ describe Rack::Lint do
     assert_lint 0
     assert_lint 1
     assert_lint nil
-    assert_lint nil, ''
-    assert_lint 1, ''
+    assert_lint nil, ''.dup
+    assert_lint 1, ''.dup
   end
 end
 
diff --git a/test/spec_lobster.rb b/test/spec_lobster.rb
index fd4e7082..9f3b9a89 100644
--- a/test/spec_lobster.rb
+++ b/test/spec_lobster.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/lobster'
 require 'rack/lint'
 require 'rack/mock'
diff --git a/test/spec_lock.rb b/test/spec_lock.rb
index aa3efa54..cd9e1230 100644
--- a/test/spec_lock.rb
+++ b/test/spec_lock.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/lint'
 require 'rack/lock'
 require 'rack/mock'
@@ -44,7 +46,7 @@ describe Rack::Lock do
         def each; %w{ hi mom }.each { |x| yield x }; end
       }.new
 
-      app = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, response] })
+      app = lock_app(lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, response] })
       response = app.call(env)[2]
       list = []
       response.each { |x| list << x }
@@ -58,7 +60,7 @@ describe Rack::Lock do
       res = ['Hello World']
       def res.to_path ; "/tmp/hello.txt" ; end
 
-      app = Rack::Lock.new(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, res] }, lock)
+      app = Rack::Lock.new(lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, res] }, lock)
       body = app.call(env)[2]
 
       body.must_respond_to :to_path
@@ -66,11 +68,11 @@ describe Rack::Lock do
     end
 
     it 'not delegate to_path if body does not implement it' do
-      env  = Rack::MockRequest.env_for("/")
+      env = Rack::MockRequest.env_for("/")
 
       res = ['Hello World']
 
-      app = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, res] })
+      app = lock_app(lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, res] })
       body = app.call(env)[2]
 
       body.wont_respond_to :to_path
@@ -85,7 +87,7 @@ describe Rack::Lock do
       def close; @close_called = true; end
     }.new
 
-    app = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, response] })
+    app = lock_app(lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, response] })
     app.call(env)
     response.close_called.must_equal false
     response.close
@@ -96,7 +98,7 @@ describe Rack::Lock do
     lock     = Lock.new
     env      = Rack::MockRequest.env_for("/")
     response = Object.new
-    app      = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, response] }, lock)
+    app      = lock_app(lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, response] }, lock)
     lock.synchronized.must_equal false
     response = app.call(env)[2]
     lock.synchronized.must_equal true
@@ -106,7 +108,7 @@ describe Rack::Lock do
 
   it "return value from app" do
     env  = Rack::MockRequest.env_for("/")
-    body = [200, {"Content-Type" => "text/plain"}, %w{ hi mom }]
+    body = [200, { "Content-Type" => "text/plain" }, %w{ hi mom }]
     app  = lock_app(lambda { |inner_env| body })
 
     res = app.call(env)
@@ -118,7 +120,7 @@ describe Rack::Lock do
   it "call synchronize on lock" do
     lock = Lock.new
     env = Rack::MockRequest.env_for("/")
-    app = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, %w{ a b c }] }, lock)
+    app = lock_app(lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, %w{ a b c }] }, lock)
     lock.synchronized.must_equal false
     app.call(env)
     lock.synchronized.must_equal true
@@ -143,11 +145,12 @@ describe Rack::Lock do
   it "set multithread flag to false" do
     app = lock_app(lambda { |env|
       env['rack.multithread'].must_equal false
-      [200, {"Content-Type" => "text/plain"}, %w{ a b c }]
+      [200, { "Content-Type" => "text/plain" }, %w{ a b c }]
     }, false)
     env = Rack::MockRequest.env_for("/")
     env['rack.multithread'].must_equal true
-    app.call(env)
+    _, _, body = app.call(env)
+    body.close
     env['rack.multithread'].must_equal true
   end
 
@@ -157,7 +160,7 @@ describe Rack::Lock do
         env['rack.multithread'].must_equal true
         super
       end
-    }.new(lambda { |env| [200, {"Content-Type" => "text/plain"}, %w{ a b c }] })
+    }.new(lambda { |env| [200, { "Content-Type" => "text/plain" }, %w{ a b c }] })
     Rack::Lint.new(app).call(Rack::MockRequest.env_for("/"))
   end
 
@@ -169,7 +172,7 @@ describe Rack::Lock do
       def unlock() @unlocked = true end
     end.new
     env = Rack::MockRequest.env_for("/")
-    app = lock_app(proc { [200, {"Content-Type" => "text/plain"}, []] }, lock)
+    app = lock_app(proc { [200, { "Content-Type" => "text/plain" }, []] }, lock)
     lambda { app.call(env) }.must_raise Exception
     lock.unlocked?.must_equal false
   end
@@ -179,7 +182,7 @@ describe Rack::Lock do
       attr_reader :env
       def initialize(env) @env = env end
     end
-    app = Rack::Lock.new lambda { |env| [200, {"Content-Type" => "text/plain"}, proxy.new(env)] }
+    app = Rack::Lock.new lambda { |env| [200, { "Content-Type" => "text/plain" }, proxy.new(env)] }
     response = app.call(Rack::MockRequest.env_for("/"))[2]
     response.env['rack.multithread'].must_equal false
   end
@@ -191,4 +194,13 @@ describe Rack::Lock do
     lambda { app.call(env) }.must_raise Exception
     lock.synchronized.must_equal false
   end
+
+  it "not replace the environment" do
+    env  = Rack::MockRequest.env_for("/")
+    app  = lock_app(lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, [inner_env.object_id.to_s]] })
+
+    _, _, body = app.call(env)
+
+    body.to_enum.to_a.must_equal [env.object_id.to_s]
+  end
 end
diff --git a/test/spec_logger.rb b/test/spec_logger.rb
index ea503e1d..f453b14d 100644
--- a/test/spec_logger.rb
+++ b/test/spec_logger.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'stringio'
 require 'rack/lint'
 require 'rack/logger'
@@ -11,7 +13,7 @@ describe Rack::Logger do
     log.info("Program started")
     log.warn("Nothing to do!")
 
-    [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]]
+    [200, { 'Content-Type' => 'text/plain' }, ["Hello, World!"]]
   }
 
   it "conform to Rack::Lint" do
diff --git a/test/spec_media_type.rb b/test/spec_media_type.rb
index ef054364..7d52b4d4 100644
--- a/test/spec_media_type.rb
+++ b/test/spec_media_type.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/media_type'
 
 describe Rack::MediaType do
@@ -8,7 +10,7 @@ describe Rack::MediaType do
     before { @content_type = nil }
 
     it '#type is nil' do
-      Rack::MediaType.type(@content_type).must_equal nil
+      Rack::MediaType.type(@content_type).must_be_nil
     end
 
     it '#params is empty' do
@@ -23,7 +25,7 @@ describe Rack::MediaType do
       Rack::MediaType.type(@content_type).must_equal 'application/text'
     end
 
-    it  '#params is empty' do
+    it '#params is empty' do
       Rack::MediaType.params(@content_type).must_equal @empty_hash
     end
   end
diff --git a/test/spec_method_override.rb b/test/spec_method_override.rb
index 14ace0b1..6b01f7c9 100644
--- a/test/spec_method_override.rb
+++ b/test/spec_method_override.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'stringio'
 require 'rack/method_override'
 require 'rack/mock'
@@ -6,19 +8,33 @@ require 'rack/mock'
 describe Rack::MethodOverride do
   def app
     Rack::Lint.new(Rack::MethodOverride.new(lambda {|e|
-      [200, {"Content-Type" => "text/plain"}, []]
+      [200, { "Content-Type" => "text/plain" }, []]
     }))
   end
 
   it "not affect GET requests" do
-    env = Rack::MockRequest.env_for("/?_method=delete", :method => "GET")
+    env = Rack::MockRequest.env_for("/?_method=delete", method: "GET")
     app.call env
 
     env["REQUEST_METHOD"].must_equal "GET"
   end
 
+  it "sets rack.errors for invalid UTF8 _method values" do
+    errors = StringIO.new
+    env = Rack::MockRequest.env_for("/",
+      :method => "POST",
+      :input => "_method=\xBF".b,
+      Rack::RACK_ERRORS => errors)
+
+    app.call env
+
+    errors.rewind
+    errors.read.must_equal "Invalid string for method\n"
+    env["REQUEST_METHOD"].must_equal "POST"
+  end
+
   it "modify REQUEST_METHOD for POST requests when _method parameter is set" do
-    env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=put")
+    env = Rack::MockRequest.env_for("/", method: "POST", input: "_method=put")
     app.call env
 
     env["REQUEST_METHOD"].must_equal "PUT"
@@ -35,14 +51,14 @@ describe Rack::MethodOverride do
   end
 
   it "not modify REQUEST_METHOD if the method is unknown" do
-    env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=foo")
+    env = Rack::MockRequest.env_for("/", method: "POST", input: "_method=foo")
     app.call env
 
     env["REQUEST_METHOD"].must_equal "POST"
   end
 
   it "not modify REQUEST_METHOD when _method is nil" do
-    env = Rack::MockRequest.env_for("/", :method => "POST", :input => "foo=bar")
+    env = Rack::MockRequest.env_for("/", method: "POST", input: "foo=bar")
     app.call env
 
     env["REQUEST_METHOD"].must_equal "POST"
@@ -50,8 +66,8 @@ describe Rack::MethodOverride do
 
   it "store the original REQUEST_METHOD prior to overriding" do
     env = Rack::MockRequest.env_for("/",
-            :method => "POST",
-            :input  => "_method=options")
+            method: "POST",
+            input: "_method=options")
     app.call env
 
     env["rack.methodoverride.original_method"].must_equal "POST"
@@ -66,16 +82,29 @@ EOF
                       "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
                       "CONTENT_LENGTH" => input.size.to_s,
                       :method => "POST", :input => input)
-    begin
-      app.call env
-    rescue EOFError
-    end
+    app.call env
 
     env["REQUEST_METHOD"].must_equal "POST"
   end
 
+  it "writes error to RACK_ERRORS when given invalid multipart form data" do
+    input = <<EOF
+--AaB03x\r
+content-disposition: form-data; name="huge"; filename="huge"\r
+EOF
+    env = Rack::MockRequest.env_for("/",
+                      "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
+                      "CONTENT_LENGTH" => input.size.to_s,
+                      Rack::RACK_ERRORS => StringIO.new,
+                      :method => "POST", :input => input)
+    Rack::MethodOverride.new(proc { [200, { "Content-Type" => "text/plain" }, []] }).call env
+
+    env[Rack::RACK_ERRORS].rewind
+    env[Rack::RACK_ERRORS].read.must_match /Bad request content body/
+  end
+
   it "not modify REQUEST_METHOD for POST requests when the params are unparseable" do
-    env = Rack::MockRequest.env_for("/", :method => "POST", :input => "(%bad-params%)")
+    env = Rack::MockRequest.env_for("/", method: "POST", input: "(%bad-params%)")
     app.call env
 
     env["REQUEST_METHOD"].must_equal "POST"
diff --git a/test/spec_mime.rb b/test/spec_mime.rb
index cd40b4b5..8d1ca256 100644
--- a/test/spec_mime.rb
+++ b/test/spec_mime.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/mime'
 
 describe Rack::Mime do
@@ -19,7 +21,7 @@ describe Rack::Mime do
   end
 
   it "should support null fallbacks" do
-    Rack::Mime.mime_type('.nothing', nil).must_equal nil
+    Rack::Mime.mime_type('.nothing', nil).must_be_nil
   end
 
   it "should match exact mimes" do
diff --git a/test/spec_mock.rb b/test/spec_mock.rb
index a4d4e5a5..d7246d3f 100644
--- a/test/spec_mock.rb
+++ b/test/spec_mock.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'yaml'
 require 'rack/lint'
 require 'rack/mock'
@@ -14,9 +16,15 @@ app = Rack::Lint.new(lambda { |env|
   end
 
   body = req.head? ? "" : env.to_yaml
-  Rack::Response.new(body,
-                     req.GET["status"] || 200,
-                     "Content-Type" => "text/yaml").finish
+  response = Rack::Response.new(
+    body,
+    req.GET["status"] || 200,
+    "Content-Type" => "text/yaml"
+  )
+  response.set_cookie("session_test", { value: "session_test", domain: ".test.com", path: "/" })
+  response.set_cookie("secure_test", { value: "secure_test", domain: ".test.com",  path: "/", secure: true })
+  response.set_cookie("persistent_test", { value: "persistent_test", max_age: 15552000, path: "/" })
+  response.finish
 })
 
 describe Rack::MockRequest do
@@ -54,44 +62,53 @@ describe Rack::MockRequest do
   end
 
   it "allow GET/POST/PUT/DELETE/HEAD" do
-    res = Rack::MockRequest.new(app).get("", :input => "foo")
+    res = Rack::MockRequest.new(app).get("", input: "foo")
     env = YAML.load(res.body)
     env["REQUEST_METHOD"].must_equal "GET"
 
-    res = Rack::MockRequest.new(app).post("", :input => "foo")
+    res = Rack::MockRequest.new(app).post("", input: "foo")
     env = YAML.load(res.body)
     env["REQUEST_METHOD"].must_equal "POST"
 
-    res = Rack::MockRequest.new(app).put("", :input => "foo")
+    res = Rack::MockRequest.new(app).put("", input: "foo")
     env = YAML.load(res.body)
     env["REQUEST_METHOD"].must_equal "PUT"
 
-    res = Rack::MockRequest.new(app).patch("", :input => "foo")
+    res = Rack::MockRequest.new(app).patch("", input: "foo")
     env = YAML.load(res.body)
     env["REQUEST_METHOD"].must_equal "PATCH"
 
-    res = Rack::MockRequest.new(app).delete("", :input => "foo")
+    res = Rack::MockRequest.new(app).delete("", input: "foo")
     env = YAML.load(res.body)
     env["REQUEST_METHOD"].must_equal "DELETE"
 
-    Rack::MockRequest.env_for("/", :method => "HEAD")["REQUEST_METHOD"]
-     .must_equal "HEAD"
+    Rack::MockRequest.env_for("/", method: "HEAD")["REQUEST_METHOD"]
+      .must_equal "HEAD"
 
-    Rack::MockRequest.env_for("/", :method => "OPTIONS")["REQUEST_METHOD"]
-     .must_equal "OPTIONS"
+    Rack::MockRequest.env_for("/", method: "OPTIONS")["REQUEST_METHOD"]
+      .must_equal "OPTIONS"
   end
 
   it "set content length" do
-    env = Rack::MockRequest.env_for("/", :input => "foo")
+    env = Rack::MockRequest.env_for("/", input: "foo")
+    env["CONTENT_LENGTH"].must_equal "3"
+
+    env = Rack::MockRequest.env_for("/", input: StringIO.new("foo"))
+    env["CONTENT_LENGTH"].must_equal "3"
+
+    env = Rack::MockRequest.env_for("/", input: Tempfile.new("name").tap { |t| t << "foo" })
     env["CONTENT_LENGTH"].must_equal "3"
+
+    env = Rack::MockRequest.env_for("/", input: IO.pipe.first)
+    env["CONTENT_LENGTH"].must_be_nil
   end
 
   it "allow posting" do
-    res = Rack::MockRequest.new(app).get("", :input => "foo")
+    res = Rack::MockRequest.new(app).get("", input: "foo")
     env = YAML.load(res.body)
     env["mock.postdata"].must_equal "foo"
 
-    res = Rack::MockRequest.new(app).post("", :input => StringIO.new("foo"))
+    res = Rack::MockRequest.new(app).post("", input: StringIO.new("foo"))
     env = YAML.load(res.body)
     env["mock.postdata"].must_equal "foo"
   end
@@ -146,7 +163,7 @@ describe Rack::MockRequest do
   end
 
   it "accept params and build query string for GET requests" do
-    res = Rack::MockRequest.new(app).get("/foo?baz=2", :params => {:foo => {:bar => "1"}})
+    res = Rack::MockRequest.new(app).get("/foo?baz=2", params: { foo: { bar: "1" } })
     env = YAML.load(res.body)
     env["REQUEST_METHOD"].must_equal "GET"
     env["QUERY_STRING"].must_include "baz=2"
@@ -156,7 +173,7 @@ describe Rack::MockRequest do
   end
 
   it "accept raw input in params for GET requests" do
-    res = Rack::MockRequest.new(app).get("/foo?baz=2", :params => "foo[bar]=1")
+    res = Rack::MockRequest.new(app).get("/foo?baz=2", params: "foo[bar]=1")
     env = YAML.load(res.body)
     env["REQUEST_METHOD"].must_equal "GET"
     env["QUERY_STRING"].must_include "baz=2"
@@ -166,7 +183,7 @@ describe Rack::MockRequest do
   end
 
   it "accept params and build url encoded params for POST requests" do
-    res = Rack::MockRequest.new(app).post("/foo", :params => {:foo => {:bar => "1"}})
+    res = Rack::MockRequest.new(app).post("/foo", params: { foo: { bar: "1" } })
     env = YAML.load(res.body)
     env["REQUEST_METHOD"].must_equal "POST"
     env["QUERY_STRING"].must_equal ""
@@ -176,7 +193,7 @@ describe Rack::MockRequest do
   end
 
   it "accept raw input in params for POST requests" do
-    res = Rack::MockRequest.new(app).post("/foo", :params => "foo[bar]=1")
+    res = Rack::MockRequest.new(app).post("/foo", params: "foo[bar]=1")
     env = YAML.load(res.body)
     env["REQUEST_METHOD"].must_equal "POST"
     env["QUERY_STRING"].must_equal ""
@@ -187,7 +204,7 @@ describe Rack::MockRequest do
 
   it "accept params and build multipart encoded params for POST requests" do
     files = Rack::Multipart::UploadedFile.new(File.join(File.dirname(__FILE__), "multipart", "file1.txt"))
-    res = Rack::MockRequest.new(app).post("/foo", :params => { "submit-name" => "Larry", "files" => files })
+    res = Rack::MockRequest.new(app).post("/foo", params: { "submit-name" => "Larry", "files" => files })
     env = YAML.load(res.body)
     env["REQUEST_METHOD"].must_equal "POST"
     env["QUERY_STRING"].must_equal ""
@@ -199,18 +216,35 @@ describe Rack::MockRequest do
 
   it "behave valid according to the Rack spec" do
     url = "https://bla.example.org:9292/meh/foo?bar"
-    Rack::MockRequest.new(app).get(url, :lint => true).
+    Rack::MockRequest.new(app).get(url, lint: true).
       must_be_kind_of Rack::MockResponse
   end
 
   it "call close on the original body object" do
     called = false
     body   = Rack::BodyProxy.new(['hi']) { called = true }
-    capp   = proc { |e| [200, {'Content-Type' => 'text/plain'}, body] }
+    capp   = proc { |e| [200, { 'Content-Type' => 'text/plain' }, body] }
     called.must_equal false
-    Rack::MockRequest.new(capp).get('/', :lint => true)
+    Rack::MockRequest.new(capp).get('/', lint: true)
     called.must_equal true
   end
+
+  it "defaults encoding to ASCII 8BIT" do
+    req = Rack::MockRequest.env_for("/foo")
+
+    keys = [
+      Rack::REQUEST_METHOD,
+      Rack::SERVER_NAME,
+      Rack::SERVER_PORT,
+      Rack::QUERY_STRING,
+      Rack::PATH_INFO,
+      Rack::HTTPS,
+      Rack::RACK_URL_SCHEME
+    ]
+    keys.each do |k|
+      assert_equal Encoding::ASCII_8BIT, req[k].encoding
+    end
+  end
 end
 
 describe Rack::MockResponse do
@@ -231,7 +265,7 @@ describe Rack::MockResponse do
     res = Rack::MockRequest.new(app).get("/?status=307")
     res.must_be :redirect?
 
-    res = Rack::MockRequest.new(app).get("/?status=201", :lint => true)
+    res = Rack::MockRequest.new(app).get("/?status=201", lint: true)
     res.must_be :empty?
   end
 
@@ -246,6 +280,42 @@ describe Rack::MockResponse do
     res.location.must_be_nil
   end
 
+  it "provide access to session cookies" do
+    res = Rack::MockRequest.new(app).get("")
+    session_cookie = res.cookie("session_test")
+    session_cookie.value[0].must_equal "session_test"
+    session_cookie.domain.must_equal ".test.com"
+    session_cookie.path.must_equal "/"
+    session_cookie.secure.must_equal false
+    session_cookie.expires.must_be_nil
+  end
+
+  it "provide access to persistent cookies" do
+    res = Rack::MockRequest.new(app).get("")
+    persistent_cookie = res.cookie("persistent_test")
+    persistent_cookie.value[0].must_equal "persistent_test"
+    persistent_cookie.domain.must_be_nil
+    persistent_cookie.path.must_equal "/"
+    persistent_cookie.secure.must_equal false
+    persistent_cookie.expires.wont_be_nil
+    persistent_cookie.expires.must_be :<, (Time.now + 15552000)
+  end
+
+  it "provide access to secure cookies" do
+    res = Rack::MockRequest.new(app).get("")
+    secure_cookie = res.cookie("secure_test")
+    secure_cookie.value[0].must_equal "secure_test"
+    secure_cookie.domain.must_equal ".test.com"
+    secure_cookie.path.must_equal "/"
+    secure_cookie.secure.must_equal true
+    secure_cookie.expires.must_be_nil
+  end
+
+  it "return nil if a non existent cookie is requested" do
+    res = Rack::MockRequest.new(app).get("")
+    res.cookie("i_dont_exist").must_be_nil
+  end
+
   it "provide access to the HTTP body" do
     res = Rack::MockRequest.new(app).get("")
     res.body.must_match(/rack/)
@@ -253,7 +323,7 @@ describe Rack::MockResponse do
   end
 
   it "provide access to the Rack errors" do
-    res = Rack::MockRequest.new(app).get("/?error=foo", :lint => true)
+    res = Rack::MockRequest.new(app).get("/?error=foo", lint: true)
     res.must_be :ok?
     res.errors.wont_be :empty?
     res.errors.must_include "foo"
@@ -269,7 +339,7 @@ describe Rack::MockResponse do
 
   it "optionally make Rack errors fatal" do
     lambda {
-      Rack::MockRequest.new(app).get("/?error=foo", :fatal => true)
+      Rack::MockRequest.new(app).get("/?error=foo", fatal: true)
     }.must_raise Rack::MockRequest::FatalWarning
   end
 end
diff --git a/test/spec_multipart.rb b/test/spec_multipart.rb
index 80e49ccb..b029048e 100644
--- a/test/spec_multipart.rb
+++ b/test/spec_multipart.rb
@@ -1,6 +1,6 @@
-# coding: utf-8
+# frozen_string_literal: true
 
-require 'minitest/autorun'
+require 'minitest/global_expectations/autorun'
 require 'rack'
 require 'rack/multipart'
 require 'rack/multipart/parser'
@@ -27,7 +27,7 @@ describe Rack::Multipart do
   it "return nil if content type is not multipart" do
     env = Rack::MockRequest.env_for("/",
             "CONTENT_TYPE" => 'application/x-www-form-urlencoded')
-    Rack::Multipart.parse_multipart(env).must_equal nil
+    Rack::Multipart.parse_multipart(env).must_be_nil
   end
 
   it "parse multipart content when content type present but filename is not" do
@@ -107,11 +107,6 @@ describe Rack::Multipart do
     def rd.rewind; end
     wr.sync = true
 
-    # mock out length to make this pipe look like a Tempfile
-    def rd.length
-      1024 * 1024 * 8
-    end
-
     # write to a pipe in a background thread, this will write a lot
     # unless Rack (properly) shuts down the read end
     thr = Thread.new do
@@ -136,7 +131,7 @@ describe Rack::Multipart do
 
     fixture = {
       "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
-      "CONTENT_LENGTH" => rd.length.to_s,
+      "CONTENT_LENGTH" => (1024 * 1024 * 8).to_s,
       :input => rd,
     }
 
@@ -177,7 +172,7 @@ describe Rack::Multipart do
     c = Class.new(Rack::QueryParser::Params) do
       def initialize(*)
         super
-        @params = Hash.new{|h,k| h[k.to_s] if k.is_a?(Symbol)}
+        @params = Hash.new{|h, k| h[k.to_s] if k.is_a?(Symbol)}
       end
     end
     query_parser = Rack::QueryParser.new c, 65536, 100
@@ -206,7 +201,7 @@ describe Rack::Multipart do
 
   it "parse multipart upload file using custom tempfile class" do
     env = Rack::MockRequest.env_for("/", multipart_fixture(:text))
-    my_tempfile = ""
+    my_tempfile = "".dup
     env['rack.multipart.tempfile_factory'] = lambda { |filename, content_type| my_tempfile }
     params = Rack::Multipart.parse_multipart(env)
     params["files"][:tempfile].object_id.must_equal my_tempfile.object_id
@@ -305,11 +300,17 @@ describe Rack::Multipart do
     params["files"][:filename].must_equal "bob's flowers.jpg"
   end
 
+  it "parse multipart form with a null byte in the filename" do
+    env = Rack::MockRequest.env_for '/', multipart_fixture(:filename_with_null_byte)
+    params = Rack::Multipart.parse_multipart(env)
+    params["files"][:filename].must_equal "flowers.exe\u0000.jpg"
+  end
+
   it "not include file params if no file was selected" do
     env = Rack::MockRequest.env_for("/", multipart_fixture(:none))
     params = Rack::Multipart.parse_multipart(env)
     params["submit-name"].must_equal "Larry"
-    params["files"].must_equal nil
+    params["files"].must_be_nil
     params.keys.wont_include "files"
   end
 
@@ -362,6 +363,19 @@ describe Rack::Multipart do
     params["files"][:tempfile].read.must_equal "contents"
   end
 
+  it "parse filename with plus character" do
+    env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_plus))
+    params = Rack::Multipart.parse_multipart(env)
+    params["files"][:type].must_equal "application/octet-stream"
+    params["files"][:filename].must_equal "foo+bar"
+    params["files"][:head].must_equal "Content-Disposition: form-data; " +
+      "name=\"files\"; " +
+      "filename=\"foo+bar\"\r\n" +
+      "Content-Type: application/octet-stream\r\n"
+    params["files"][:name].must_equal "files"
+    params["files"][:tempfile].read.must_equal "contents"
+  end
+
   it "parse filename with percent escaped quotes" do
     env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_percent_escaped_quotes))
     params = Rack::Multipart.parse_multipart(env)
@@ -476,7 +490,7 @@ Content-Type: image/jpeg\r
 
   it "builds nested multipart body" do
     files = Rack::Multipart::UploadedFile.new(multipart_file("file1.txt"))
-    data  = Rack::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => files}])
+    data  = Rack::Multipart.build_multipart("people" => [{ "submit-name" => "Larry", "files" => files }])
 
     options = {
       "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
@@ -556,8 +570,8 @@ Content-Type: image/jpeg\r
   end
 
   it "return nil if no UploadedFiles were used" do
-    data = Rack::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => "contents"}])
-    data.must_equal nil
+    data = Rack::Multipart.build_multipart("people" => [{ "submit-name" => "Larry", "files" => "contents" }])
+    data.must_be_nil
   end
 
   it "raise ArgumentError if params is not a Hash" do
@@ -583,7 +597,7 @@ EOF
     env = Rack::MockRequest.env_for("/", options)
     params = Rack::Multipart.parse_multipart(env)
 
-    params.must_equal "description"=>"Very very blue"
+    params.must_equal "description" => "Very very blue"
   end
 
   it "parse multipart upload with no content-length header" do
@@ -682,7 +696,7 @@ true\r
   it "fallback to content-type for name" do
     rack_logo = File.read(multipart_file("rack-logo.png"))
 
-    data = <<-EOF
+    data = <<-EOF.dup
 --AaB03x\r
 Content-Type: text/plain\r
 \r
@@ -701,7 +715,7 @@ Content-Type: image/png\r
     options = {
       "CONTENT_TYPE" => "multipart/related; boundary=AaB03x",
       "CONTENT_LENGTH" => data.bytesize.to_s,
-      :input => StringIO.new(data)
+      :input => StringIO.new(data.dup)
     }
     env = Rack::MockRequest.env_for("/", options)
     params = Rack::Multipart.parse_multipart(env)
diff --git a/test/spec_null_logger.rb b/test/spec_null_logger.rb
index 3002d97f..1037c9fa 100644
--- a/test/spec_null_logger.rb
+++ b/test/spec_null_logger.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/lint'
 require 'rack/mock'
 require 'rack/null_logger'
@@ -7,14 +9,14 @@ describe Rack::NullLogger do
   it "act as a noop logger" do
     app = lambda { |env|
       env['rack.logger'].warn "b00m"
-      [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]]
+      [200, { 'Content-Type' => 'text/plain' }, ["Hello, World!"]]
     }
 
     logger = Rack::Lint.new(Rack::NullLogger.new(app))
 
     res = logger.call(Rack::MockRequest.env_for)
     res[0..1].must_equal [
-      200, {'Content-Type' => 'text/plain'}
+      200, { 'Content-Type' => 'text/plain' }
     ]
     res[2].to_enum.to_a.must_equal ["Hello, World!"]
   end
diff --git a/test/spec_recursive.rb b/test/spec_recursive.rb
index 4a60e600..e77d966d 100644
--- a/test/spec_recursive.rb
+++ b/test/spec_recursive.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/lint'
 require 'rack/recursive'
 require 'rack/mock'
diff --git a/test/spec_request.rb b/test/spec_request.rb
index 74f2fa87..583a367e 100644
--- a/test/spec_request.rb
+++ b/test/spec_request.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'stringio'
 require 'cgi'
 require 'rack/request'
@@ -53,7 +55,7 @@ class RackRequestTest < Minitest::Spec
     req = make_request(Rack::MockRequest.env_for("http://example.com:8080/"))
     req.set_header 'foo', 'bar'
     hash = {}
-    req.each_header do |k,v|
+    req.each_header do |k, v|
       hash[k] = v
     end
     assert_equal 'bar', hash['foo']
@@ -230,7 +232,7 @@ class RackRequestTest < Minitest::Spec
     c = Class.new(Rack::QueryParser::Params) do
       def initialize(*)
         super
-        @params = Hash.new{|h,k| h[k.to_s] if k.is_a?(Symbol)}
+        @params = Hash.new{|h, k| h[k.to_s] if k.is_a?(Symbol)}
       end
     end
     parser = Rack::QueryParser.new(c, 65536, 100)
@@ -272,7 +274,7 @@ class RackRequestTest < Minitest::Spec
 
     old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 3
     begin
-      exp = {"foo"=>{"bar"=>{"baz"=>{"qux"=>"1"}}}}
+      exp = { "foo" => { "bar" => { "baz" => { "qux" => "1" } } } }
       make_request(nested_query).GET.must_equal exp
       lambda { make_request(plain_query).GET  }.must_raise RangeError
     ensure
@@ -298,7 +300,7 @@ class RackRequestTest < Minitest::Spec
     c = Class.new(Rack::QueryParser::Params) do
       def initialize(*)
         super
-        @params = Hash.new{|h,k| h[k.to_s] if k.is_a?(Symbol)}
+        @params = Hash.new{|h, k| h[k.to_s] if k.is_a?(Symbol)}
       end
     end
     parser = Rack::QueryParser.new(c, 65536, 100)
@@ -476,7 +478,7 @@ class RackRequestTest < Minitest::Spec
 
     req = make_request \
       Rack::MockRequest.env_for("/")
-    req.referer.must_equal nil
+    req.referer.must_be_nil
   end
 
   it "extract user agent correctly" do
@@ -486,25 +488,25 @@ class RackRequestTest < Minitest::Spec
 
     req = make_request \
       Rack::MockRequest.env_for("/")
-    req.user_agent.must_equal nil
+    req.user_agent.must_be_nil
   end
 
   it "treat missing content type as nil" do
     req = make_request \
       Rack::MockRequest.env_for("/")
-    req.content_type.must_equal nil
+    req.content_type.must_be_nil
   end
 
   it "treat empty content type as nil" do
     req = make_request \
       Rack::MockRequest.env_for("/", "CONTENT_TYPE" => "")
-    req.content_type.must_equal nil
+    req.content_type.must_be_nil
   end
 
   it "return nil media type for empty content type" do
     req = make_request \
       Rack::MockRequest.env_for("/", "CONTENT_TYPE" => "")
-    req.media_type.must_equal nil
+    req.media_type.must_be_nil
   end
 
   it "cache, but invalidates the cache" do
@@ -572,6 +574,11 @@ class RackRequestTest < Minitest::Spec
     request.must_be :ssl?
   end
 
+  it "prevents scheme abuse" do
+    request = make_request(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_SCHEME' => 'a."><script>alert(1)</script>'))
+    request.scheme.must_equal 'http'
+  end
+
   it "parse cookies" do
     req = make_request \
       Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m")
@@ -722,7 +729,7 @@ class RackRequestTest < Minitest::Spec
   end
 
   it "provide setters" do
-    req = make_request(e=Rack::MockRequest.env_for(""))
+    req = make_request(e = Rack::MockRequest.env_for(""))
     req.script_name.must_equal ""
     req.script_name = "/foo"
     req.script_name.must_equal "/foo"
@@ -953,7 +960,7 @@ EOF
 --AaB03x\r
 content-disposition: form-data; name="huge"; filename="huge"\r
 \r
-#{"x"*32768}\r
+#{"x" * 32768}\r
 --AaB03x\r
 content-disposition: form-data; name="mean"; filename="mean"\r
 \r
@@ -1072,7 +1079,7 @@ EOF
 content-disposition: form-data; name="fileupload"; filename="junk.a"\r
 content-type: application/octet-stream\r
 \r
-#{[0x36,0xCF,0x0A,0xF8].pack('c*')}\r
+#{[0x36, 0xCF, 0x0A, 0xF8].pack('c*')}\r
 --AaB03x--\r
 EOF
 
@@ -1092,7 +1099,7 @@ EOF
     rack_input.rewind
 
     req = make_request Rack::MockRequest.env_for("/",
-                      "rack.request.form_hash" => {'foo' => 'bar'},
+                      "rack.request.form_hash" => { 'foo' => 'bar' },
                       "rack.request.form_input" => rack_input,
                       :input => rack_input)
 
@@ -1103,10 +1110,10 @@ EOF
     app = lambda { |env|
       content = make_request(env).POST["file"].inspect
       size = content.bytesize
-      [200, {"Content-Type" => "text/html", "Content-Length" => size.to_s}, [content]]
+      [200, { "Content-Type" => "text/html", "Content-Length" => size.to_s }, [content]]
     }
 
-    input = <<EOF
+    input = <<EOF.dup
 --AaB03x\r
 content-disposition: form-data; name="reply"\r
 \r
@@ -1216,6 +1223,20 @@ EOF
     res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '127.0.0.1, 3.4.5.6'
     res.body.must_equal '3.4.5.6'
 
+    # IPv6 format with optional port: "[2001:db8:cafe::17]:47011"
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '[2001:db8:cafe::17]:47011'
+    res.body.must_equal '2001:db8:cafe::17'
+
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '1.2.3.4, [2001:db8:cafe::17]:47011'
+    res.body.must_equal '2001:db8:cafe::17'
+
+    # IPv4 format with optional port: "192.0.2.43:47011"
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '192.0.2.43:47011'
+    res.body.must_equal '192.0.2.43'
+
+    res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '1.2.3.4, 192.0.2.43:47011'
+    res.body.must_equal '192.0.2.43'
+
     res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1'
     res.body.must_equal 'unknown'
 
@@ -1281,28 +1302,45 @@ EOF
     res.body.must_equal '2.2.2.3'
   end
 
-  it "regard local addresses as proxies" do
+  it "preserves ip for trusted proxy chain" do
+    mock = Rack::MockRequest.new(Rack::Lint.new(ip_app))
+    res = mock.get '/',
+      'HTTP_X_FORWARDED_FOR' => '192.168.0.11, 192.168.0.7',
+      'HTTP_CLIENT_IP' => '127.0.0.1'
+    res.body.must_equal '192.168.0.11'
+
+  end
+
+  it "uses a custom trusted proxy filter" do
+    old_ip = Rack::Request.ip_filter
+    Rack::Request.ip_filter = lambda { |ip| ip == 'foo' }
+    req = make_request(Rack::MockRequest.env_for("/"))
+    assert req.trusted_proxy?('foo')
+    Rack::Request.ip_filter = old_ip
+  end
+
+  it "regards local addresses as proxies" do
     req = make_request(Rack::MockRequest.env_for("/"))
-    req.trusted_proxy?('127.0.0.1').must_equal 0
-    req.trusted_proxy?('10.0.0.1').must_equal 0
-    req.trusted_proxy?('172.16.0.1').must_equal 0
-    req.trusted_proxy?('172.20.0.1').must_equal 0
-    req.trusted_proxy?('172.30.0.1').must_equal 0
-    req.trusted_proxy?('172.31.0.1').must_equal 0
-    req.trusted_proxy?('192.168.0.1').must_equal 0
-    req.trusted_proxy?('::1').must_equal 0
-    req.trusted_proxy?('fd00::').must_equal 0
-    req.trusted_proxy?('localhost').must_equal 0
-    req.trusted_proxy?('unix').must_equal 0
-    req.trusted_proxy?('unix:/tmp/sock').must_equal 0
-
-    req.trusted_proxy?("unix.example.org").must_equal nil
-    req.trusted_proxy?("example.org\n127.0.0.1").must_equal nil
-    req.trusted_proxy?("127.0.0.1\nexample.org").must_equal nil
-    req.trusted_proxy?("11.0.0.1").must_equal nil
-    req.trusted_proxy?("172.15.0.1").must_equal nil
-    req.trusted_proxy?("172.32.0.1").must_equal nil
-    req.trusted_proxy?("2001:470:1f0b:18f8::1").must_equal nil
+    req.trusted_proxy?('127.0.0.1').must_equal true
+    req.trusted_proxy?('10.0.0.1').must_equal true
+    req.trusted_proxy?('172.16.0.1').must_equal true
+    req.trusted_proxy?('172.20.0.1').must_equal true
+    req.trusted_proxy?('172.30.0.1').must_equal true
+    req.trusted_proxy?('172.31.0.1').must_equal true
+    req.trusted_proxy?('192.168.0.1').must_equal true
+    req.trusted_proxy?('::1').must_equal true
+    req.trusted_proxy?('fd00::').must_equal true
+    req.trusted_proxy?('localhost').must_equal true
+    req.trusted_proxy?('unix').must_equal true
+    req.trusted_proxy?('unix:/tmp/sock').must_equal true
+
+    req.trusted_proxy?("unix.example.org").must_equal false
+    req.trusted_proxy?("example.org\n127.0.0.1").must_equal false
+    req.trusted_proxy?("127.0.0.1\nexample.org").must_equal false
+    req.trusted_proxy?("11.0.0.1").must_equal false
+    req.trusted_proxy?("172.15.0.1").must_equal false
+    req.trusted_proxy?("172.32.0.1").must_equal false
+    req.trusted_proxy?("2001:470:1f0b:18f8::1").must_equal false
   end
 
   it "sets the default session to an empty hash" do
@@ -1312,7 +1350,7 @@ EOF
 
   class MyRequest < Rack::Request
     def params
-      {:foo => "bar"}
+      { foo: "bar" }
     end
   end
 
@@ -1325,7 +1363,7 @@ EOF
 
     req2 = MyRequest.new(env)
     req2.GET.must_equal "foo" => "bar"
-    req2.params.must_equal :foo => "bar"
+    req2.params.must_equal foo: "bar"
   end
 
   it "allow parent request to be instantiated after subclass request" do
@@ -1333,7 +1371,7 @@ EOF
 
     req1 = MyRequest.new(env)
     req1.GET.must_equal "foo" => "bar"
-    req1.params.must_equal :foo => "bar"
+    req1.params.must_equal foo: "bar"
 
     req2 = make_request(env)
     req2.GET.must_equal "foo" => "bar"
diff --git a/test/spec_response.rb b/test/spec_response.rb
index 02e51435..3cd56664 100644
--- a/test/spec_response.rb
+++ b/test/spec_response.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack'
 require 'rack/response'
 require 'stringio'
@@ -9,7 +11,7 @@ describe Rack::Response do
     cc = 'foo'
     response.cache_control = cc
     assert_equal cc, response.cache_control
-    assert_equal cc, response.to_a[2]['Cache-Control']
+    assert_equal cc, response.to_a[1]['Cache-Control']
   end
 
   it 'has an etag method' do
@@ -17,7 +19,7 @@ describe Rack::Response do
     etag = 'foo'
     response.etag = etag
     assert_equal etag, response.etag
-    assert_equal etag, response.to_a[2]['ETag']
+    assert_equal etag, response.to_a[1]['ETag']
   end
 
   it "have sensible default values" do
@@ -55,11 +57,21 @@ describe Rack::Response do
 
   it "can set and read headers" do
     response = Rack::Response.new
-    response["Content-Type"].must_equal nil
+    response["Content-Type"].must_be_nil
     response["Content-Type"] = "text/plain"
     response["Content-Type"].must_equal "text/plain"
   end
 
+  it "doesn't mutate given headers" do
+    [{}, Rack::Utils::HeaderHash.new].each do |header|
+      response = Rack::Response.new([], 200, header)
+      response.header["Content-Type"] = "text/plain"
+      response.header["Content-Type"].must_equal "text/plain"
+
+      header.wont_include("Content-Type")
+    end
+  end
+
   it "can override the initial Content-Type with a different case" do
     response = Rack::Response.new("", 200, "content-type" => "text/plain")
     response["Content-Type"].must_equal "text/plain"
@@ -78,103 +90,121 @@ describe Rack::Response do
 
   it "can set cookies with the same name for multiple domains" do
     response = Rack::Response.new
-    response.set_cookie "foo", {:value => "bar", :domain => "sample.example.com"}
-    response.set_cookie "foo", {:value => "bar", :domain => ".example.com"}
+    response.set_cookie "foo", { value: "bar", domain: "sample.example.com" }
+    response.set_cookie "foo", { value: "bar", domain: ".example.com" }
     response["Set-Cookie"].must_equal ["foo=bar; domain=sample.example.com", "foo=bar; domain=.example.com"].join("\n")
   end
 
   it "formats the Cookie expiration date accordingly to RFC 6265" do
     response = Rack::Response.new
 
-    response.set_cookie "foo", {:value => "bar", :expires => Time.now+10}
+    response.set_cookie "foo", { value: "bar", expires: Time.now + 10 }
     response["Set-Cookie"].must_match(
       /expires=..., \d\d ... \d\d\d\d \d\d:\d\d:\d\d .../)
   end
 
   it "can set secure cookies" do
     response = Rack::Response.new
-    response.set_cookie "foo", {:value => "bar", :secure => true}
+    response.set_cookie "foo", { value: "bar", secure: true }
     response["Set-Cookie"].must_equal "foo=bar; secure"
   end
 
   it "can set http only cookies" do
     response = Rack::Response.new
-    response.set_cookie "foo", {:value => "bar", :httponly => true}
+    response.set_cookie "foo", { value: "bar", httponly: true }
     response["Set-Cookie"].must_equal "foo=bar; HttpOnly"
   end
 
   it "can set http only cookies with :http_only" do
     response = Rack::Response.new
-    response.set_cookie "foo", {:value => "bar", :http_only => true}
+    response.set_cookie "foo", { value: "bar", http_only: true }
     response["Set-Cookie"].must_equal "foo=bar; HttpOnly"
   end
 
   it "can set prefers :httponly for http only cookie setting when :httponly and :http_only provided" do
     response = Rack::Response.new
-    response.set_cookie "foo", {:value => "bar", :httponly => false, :http_only => true}
+    response.set_cookie "foo", { value: "bar", httponly: false, http_only: true }
     response["Set-Cookie"].must_equal "foo=bar"
   end
 
+  it "can set SameSite cookies with symbol value :none" do
+    response = Rack::Response.new
+    response.set_cookie "foo", { value: "bar", same_site: :none }
+    response["Set-Cookie"].must_equal "foo=bar; SameSite=None"
+  end
+
+  it "can set SameSite cookies with symbol value :None" do
+    response = Rack::Response.new
+    response.set_cookie "foo", { value: "bar", same_site: :None }
+    response["Set-Cookie"].must_equal "foo=bar; SameSite=None"
+  end
+
+  it "can set SameSite cookies with string value 'None'" do
+    response = Rack::Response.new
+    response.set_cookie "foo", { value: "bar", same_site: "None" }
+    response["Set-Cookie"].must_equal "foo=bar; SameSite=None"
+  end
+
   it "can set SameSite cookies with symbol value :lax" do
     response = Rack::Response.new
-    response.set_cookie "foo", {:value => "bar", :same_site => :lax}
+    response.set_cookie "foo", { value: "bar", same_site: :lax }
     response["Set-Cookie"].must_equal "foo=bar; SameSite=Lax"
   end
 
   it "can set SameSite cookies with symbol value :Lax" do
     response = Rack::Response.new
-    response.set_cookie "foo", {:value => "bar", :same_site => :lax}
+    response.set_cookie "foo", { value: "bar", same_site: :lax }
     response["Set-Cookie"].must_equal "foo=bar; SameSite=Lax"
   end
 
   it "can set SameSite cookies with string value 'Lax'" do
     response = Rack::Response.new
-    response.set_cookie "foo", {:value => "bar", :same_site => "Lax"}
+    response.set_cookie "foo", { value: "bar", same_site: "Lax" }
     response["Set-Cookie"].must_equal "foo=bar; SameSite=Lax"
   end
 
   it "can set SameSite cookies with boolean value true" do
     response = Rack::Response.new
-    response.set_cookie "foo", {:value => "bar", :same_site => true}
+    response.set_cookie "foo", { value: "bar", same_site: true }
     response["Set-Cookie"].must_equal "foo=bar; SameSite=Strict"
   end
 
   it "can set SameSite cookies with symbol value :strict" do
     response = Rack::Response.new
-    response.set_cookie "foo", {:value => "bar", :same_site => :strict}
+    response.set_cookie "foo", { value: "bar", same_site: :strict }
     response["Set-Cookie"].must_equal "foo=bar; SameSite=Strict"
   end
 
   it "can set SameSite cookies with symbol value :Strict" do
     response = Rack::Response.new
-    response.set_cookie "foo", {:value => "bar", :same_site => :Strict}
+    response.set_cookie "foo", { value: "bar", same_site: :Strict }
     response["Set-Cookie"].must_equal "foo=bar; SameSite=Strict"
   end
 
   it "can set SameSite cookies with string value 'Strict'" do
     response = Rack::Response.new
-    response.set_cookie "foo", {:value => "bar", :same_site => "Strict"}
+    response.set_cookie "foo", { value: "bar", same_site: "Strict" }
     response["Set-Cookie"].must_equal "foo=bar; SameSite=Strict"
   end
 
   it "validates the SameSite option value" do
     response = Rack::Response.new
     lambda {
-      response.set_cookie "foo", {:value => "bar", :same_site => "Foo"}
+      response.set_cookie "foo", { value: "bar", same_site: "Foo" }
     }.must_raise(ArgumentError).
       message.must_match(/Invalid SameSite value: "Foo"/)
   end
 
   it "can set SameSite cookies with symbol value" do
     response = Rack::Response.new
-    response.set_cookie "foo", {:value => "bar", :same_site => :Strict}
+    response.set_cookie "foo", { value: "bar", same_site: :Strict }
     response["Set-Cookie"].must_equal "foo=bar; SameSite=Strict"
   end
 
   [ nil, false ].each do |non_truthy|
     it "omits SameSite attribute given a #{non_truthy.inspect} value" do
       response = Rack::Response.new
-      response.set_cookie "foo", {:value => "bar", :same_site => non_truthy}
+      response.set_cookie "foo", { value: "bar", same_site: non_truthy }
       response["Set-Cookie"].must_equal "foo=bar"
     end
   end
@@ -186,32 +216,32 @@ describe Rack::Response do
     response.delete_cookie "foo"
     response["Set-Cookie"].must_equal [
       "foo2=bar2",
-      "foo=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
+      "foo=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
     ].join("\n")
   end
 
   it "can delete cookies with the same name from multiple domains" do
     response = Rack::Response.new
-    response.set_cookie "foo", {:value => "bar", :domain => "sample.example.com"}
-    response.set_cookie "foo", {:value => "bar", :domain => ".example.com"}
+    response.set_cookie "foo", { value: "bar", domain: "sample.example.com" }
+    response.set_cookie "foo", { value: "bar", domain: ".example.com" }
     response["Set-Cookie"].must_equal ["foo=bar; domain=sample.example.com", "foo=bar; domain=.example.com"].join("\n")
-    response.delete_cookie "foo", :domain => ".example.com"
-    response["Set-Cookie"].must_equal ["foo=bar; domain=sample.example.com", "foo=; domain=.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"].join("\n")
-    response.delete_cookie "foo", :domain => "sample.example.com"
-    response["Set-Cookie"].must_equal ["foo=; domain=.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000",
-                                         "foo=; domain=sample.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"].join("\n")
+    response.delete_cookie "foo", domain: ".example.com"
+    response["Set-Cookie"].must_equal ["foo=bar; domain=sample.example.com", "foo=; domain=.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"].join("\n")
+    response.delete_cookie "foo", domain: "sample.example.com"
+    response["Set-Cookie"].must_equal ["foo=; domain=.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT",
+                                         "foo=; domain=sample.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"].join("\n")
   end
 
   it "can delete cookies with the same name with different paths" do
     response = Rack::Response.new
-    response.set_cookie "foo", {:value => "bar", :path => "/"}
-    response.set_cookie "foo", {:value => "bar", :path => "/path"}
+    response.set_cookie "foo", { value: "bar", path: "/" }
+    response.set_cookie "foo", { value: "bar", path: "/path" }
     response["Set-Cookie"].must_equal ["foo=bar; path=/",
                                          "foo=bar; path=/path"].join("\n")
 
-    response.delete_cookie "foo", :path => "/path"
+    response.delete_cookie "foo", path: "/path"
     response["Set-Cookie"].must_equal ["foo=bar; path=/",
-                                         "foo=; path=/path; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"].join("\n")
+                                         "foo=; path=/path; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"].join("\n")
   end
 
   it "can do redirects" do
@@ -231,12 +261,12 @@ describe Rack::Response do
   it "has a useful constructor" do
     r = Rack::Response.new("foo")
     status, header, body = r.finish
-    str = ""; body.each { |part| str << part }
+    str = "".dup; body.each { |part| str << part }
     str.must_equal "foo"
 
     r = Rack::Response.new(["foo", "bar"])
     status, header, body = r.finish
-    str = ""; body.each { |part| str << part }
+    str = "".dup; body.each { |part| str << part }
     str.must_equal "foobar"
 
     object_with_each = Object.new
@@ -247,7 +277,7 @@ describe Rack::Response do
     r = Rack::Response.new(object_with_each)
     r.write "foo"
     status, header, body = r.finish
-    str = ""; body.each { |part| str << part }
+    str = "".dup; body.each { |part| str << part }
     str.must_equal "foobarfoo"
 
     r = Rack::Response.new([], 500)
@@ -263,7 +293,7 @@ describe Rack::Response do
       res.write "foo"
     }
     status, _, body = r.finish
-    str = ""; body.each { |part| str << part }
+    str = "".dup; body.each { |part| str << part }
     str.must_equal "foo"
     status.must_equal 404
   end
@@ -271,10 +301,10 @@ describe Rack::Response do
   it "doesn't return invalid responses" do
     r = Rack::Response.new(["foo", "bar"], 204)
     _, header, body = r.finish
-    str = ""; body.each { |part| str << part }
+    str = "".dup; body.each { |part| str << part }
     str.must_be :empty?
-    header["Content-Type"].must_equal nil
-    header['Content-Length'].must_equal nil
+    header["Content-Type"].must_be_nil
+    header['Content-Length'].must_be_nil
 
     lambda {
       Rack::Response.new(Object.new)
@@ -410,7 +440,7 @@ describe Rack::Response do
     res.body.must_be :closed?
   end
 
-  it "calls close on #body when 204, 205, or 304" do
+  it "calls close on #body when 204 or 304" do
     res = Rack::Response.new
     res.body = StringIO.new
     res.finish
@@ -424,7 +454,7 @@ describe Rack::Response do
     res.body = StringIO.new
     res.status = 205
     _, _, b = res.finish
-    res.body.must_be :closed?
+    res.body.wont_be :closed?
     b.wont_equal res.body
 
     res.body = StringIO.new
diff --git a/test/spec_rewindable_input.rb b/test/spec_rewindable_input.rb
index 5adfba1d..6bb5f5cf 100644
--- a/test/spec_rewindable_input.rb
+++ b/test/spec_rewindable_input.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'stringio'
 require 'rack/rewindable_input'
 
@@ -26,14 +28,14 @@ module RewindableTest
   end
 
   it "be able to handle to read(length, buffer)" do
-    buffer = ""
+    buffer = "".dup
     result = @rio.read(1, buffer)
     result.must_equal "h"
     result.object_id.must_equal buffer.object_id
   end
 
   it "be able to handle to read(nil, buffer)" do
-    buffer = ""
+    buffer = "".dup
     result = @rio.read(nil, buffer)
     result.must_equal "hello world"
     result.object_id.must_equal buffer.object_id
@@ -95,7 +97,7 @@ end
 describe Rack::RewindableInput do
   describe "given an IO object that is already rewindable" do
     def setup
-      @io = StringIO.new("hello world")
+      @io = StringIO.new("hello world".dup)
       super
     end
 
@@ -104,7 +106,7 @@ describe Rack::RewindableInput do
 
   describe "given an IO object that is not rewindable" do
     def setup
-      @io = StringIO.new("hello world")
+      @io = StringIO.new("hello world".dup)
       @io.instance_eval do
         undef :rewind
       end
@@ -116,7 +118,7 @@ describe Rack::RewindableInput do
 
   describe "given an IO object whose rewind method raises Errno::ESPIPE" do
     def setup
-      @io = StringIO.new("hello world")
+      @io = StringIO.new("hello world".dup)
       def @io.rewind
         raise Errno::ESPIPE, "You can't rewind this!"
       end
diff --git a/test/spec_runtime.rb b/test/spec_runtime.rb
index f7f52ad9..10e561de 100644
--- a/test/spec_runtime.rb
+++ b/test/spec_runtime.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/lint'
 require 'rack/mock'
 require 'rack/runtime'
@@ -13,25 +15,25 @@ describe Rack::Runtime do
   end
 
   it "sets X-Runtime is none is set" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, "Hello, World!"] }
     response = runtime_app(app).call(request)
     response[1]['X-Runtime'].must_match(/[\d\.]+/)
   end
 
   it "doesn't set the X-Runtime if it is already set" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain', "X-Runtime" => "foobar"}, "Hello, World!"] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain', "X-Runtime" => "foobar" }, "Hello, World!"] }
     response = runtime_app(app).call(request)
     response[1]['X-Runtime'].must_equal "foobar"
   end
 
   it "allow a suffix to be set" do
-    app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
+    app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, "Hello, World!"] }
     response = runtime_app(app, "Test").call(request)
     response[1]['X-Runtime-Test'].must_match(/[\d\.]+/)
   end
 
   it "allow multiple timers to be set" do
-    app = lambda { |env| sleep 0.1; [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
+    app = lambda { |env| sleep 0.1; [200, { 'Content-Type' => 'text/plain' }, "Hello, World!"] }
     runtime = runtime_app(app, "App")
 
     # wrap many times to guarantee a measurable difference
diff --git a/test/spec_sendfile.rb b/test/spec_sendfile.rb
index 18689857..cbed8db3 100644
--- a/test/spec_sendfile.rb
+++ b/test/spec_sendfile.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'fileutils'
 require 'rack/lint'
 require 'rack/sendfile'
@@ -6,22 +8,22 @@ require 'rack/mock'
 require 'tmpdir'
 
 describe Rack::Sendfile do
-  def sendfile_body
-    FileUtils.touch File.join(Dir.tmpdir,  "rack_sendfile")
+  def sendfile_body(filename = "rack_sendfile")
+    FileUtils.touch File.join(Dir.tmpdir,  filename)
     res = ['Hello World']
-    def res.to_path ; File.join(Dir.tmpdir,  "rack_sendfile") ; end
+    res.define_singleton_method(:to_path) { File.join(Dir.tmpdir,  filename) }
     res
   end
 
-  def simple_app(body=sendfile_body)
-    lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
+  def simple_app(body = sendfile_body)
+    lambda { |env| [200, { 'Content-Type' => 'text/plain' }, body] }
   end
 
   def sendfile_app(body, mappings = [])
     Rack::Lint.new Rack::Sendfile.new(simple_app(body), nil, mappings)
   end
 
-  def request(headers={}, body=sendfile_body, mappings=[])
+  def request(headers = {}, body = sendfile_body, mappings = [])
     yield Rack::MockRequest.new(sendfile_app(body, mappings)).get('/', headers)
   end
 
@@ -72,6 +74,19 @@ describe Rack::Sendfile do
     end
   end
 
+  it "sets X-Accel-Redirect response header to percent-encoded path" do
+    headers = {
+      'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect',
+      'HTTP_X_ACCEL_MAPPING' => "#{Dir.tmpdir}/=/foo/bar%/"
+    }
+    request headers, sendfile_body('file_with_%_?_symbol') do |response|
+      response.must_be :ok?
+      response.body.must_be :empty?
+      response.headers['Content-Length'].must_equal '0'
+      response.headers['X-Accel-Redirect'].must_equal '/foo/bar%25/file_with_%25_%3F_symbol'
+    end
+  end
+
   it 'writes to rack.error when no X-Accel-Mapping is specified' do
     request 'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect' do |response|
       response.must_be :ok?
@@ -82,7 +97,7 @@ describe Rack::Sendfile do
   end
 
   it 'does nothing when body does not respond to #to_path' do
-    request({'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile'}, ['Not a file...']) do |response|
+    request({ 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' }, ['Not a file...']) do |response|
       response.body.must_equal 'Not a file...'
       response.headers.wont_include 'X-Sendfile'
     end
@@ -104,14 +119,49 @@ describe Rack::Sendfile do
         ["#{dir2}/", '/wibble/']
       ]
 
-      request({'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect'}, first_body, mappings) do |response|
+      request({ 'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect' }, first_body, mappings) do |response|
+        response.must_be :ok?
+        response.body.must_be :empty?
+        response.headers['Content-Length'].must_equal '0'
+        response.headers['X-Accel-Redirect'].must_equal '/foo/bar/rack_sendfile'
+      end
+
+      request({ 'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect' }, second_body, mappings) do |response|
+        response.must_be :ok?
+        response.body.must_be :empty?
+        response.headers['Content-Length'].must_equal '0'
+        response.headers['X-Accel-Redirect'].must_equal '/wibble/rack_sendfile'
+      end
+    ensure
+      FileUtils.remove_entry_secure dir1
+      FileUtils.remove_entry_secure dir2
+    end
+  end
+
+  it "sets X-Accel-Redirect response header and discards body when initialized with multiple mappings via header" do
+    begin
+      dir1 = Dir.mktmpdir
+      dir2 = Dir.mktmpdir
+
+      first_body = open_file(File.join(dir1, 'rack_sendfile'))
+      first_body.puts 'hello world'
+
+      second_body = open_file(File.join(dir2, 'rack_sendfile'))
+      second_body.puts 'goodbye world'
+
+      headers = {
+        'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect',
+        'HTTP_X_ACCEL_MAPPING' => "#{dir1}/=/foo/bar/, #{dir2}/=/wibble/"
+      }
+
+      request(headers, first_body) do |response|
         response.must_be :ok?
         response.body.must_be :empty?
         response.headers['Content-Length'].must_equal '0'
         response.headers['X-Accel-Redirect'].must_equal '/foo/bar/rack_sendfile'
       end
 
-      request({'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect'}, second_body, mappings) do |response|
+      request(headers, second_body) do |response|
         response.must_be :ok?
         response.body.must_be :empty?
         response.headers['Content-Length'].must_equal '0'
diff --git a/test/spec_server.rb b/test/spec_server.rb
index a3690bce..b09caf03 100644
--- a/test/spec_server.rb
+++ b/test/spec_server.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack'
 require 'rack/server'
 require 'tempfile'
@@ -15,7 +17,7 @@ describe Rack::Server do
   before { SPEC_ARGV[0..-1] = [] }
 
   def app
-    lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['success']] }
+    lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ['success']] }
   end
 
   def with_stderr
@@ -26,12 +28,12 @@ describe Rack::Server do
   end
 
   it "overrides :config if :app is passed in" do
-    server = Rack::Server.new(:app => "FOO")
+    server = Rack::Server.new(app: "FOO")
     server.app.must_equal "FOO"
   end
 
   it "prefer to use :builder when it is passed in" do
-    server = Rack::Server.new(:builder => "run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['success']] }")
+    server = Rack::Server.new(builder: "run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['success']] }")
     server.app.class.must_equal Proc
     Rack::MockRequest.new(server.app).get("/").body.to_s.must_equal 'success'
   end
@@ -39,13 +41,13 @@ describe Rack::Server do
   it "allow subclasses to override middleware" do
     server = Class.new(Rack::Server).class_eval { def middleware; Hash.new [] end; self }
     server.middleware['deployment'].wont_equal []
-    server.new(:app => 'foo').middleware['deployment'].must_equal []
+    server.new(app: 'foo').middleware['deployment'].must_equal []
   end
 
   it "allow subclasses to override default middleware" do
     server = Class.new(Rack::Server).instance_eval { def default_middleware_by_environment; Hash.new [] end; self }
     server.middleware['deployment'].must_equal []
-    server.new(:app => 'foo').middleware['deployment'].must_equal []
+    server.new(app: 'foo').middleware['deployment'].must_equal []
   end
 
   it "only provide default middleware for development and deployment environments" do
@@ -53,39 +55,39 @@ describe Rack::Server do
   end
 
   it "always return an empty array for unknown environments" do
-    server = Rack::Server.new(:app => 'foo')
+    server = Rack::Server.new(app: 'foo')
     server.middleware['production'].must_equal []
   end
 
   it "not include Rack::Lint in deployment environment" do
-    server = Rack::Server.new(:app => 'foo')
+    server = Rack::Server.new(app: 'foo')
     server.middleware['deployment'].flatten.wont_include Rack::Lint
   end
 
   it "not include Rack::ShowExceptions in deployment environment" do
-    server = Rack::Server.new(:app => 'foo')
+    server = Rack::Server.new(app: 'foo')
     server.middleware['deployment'].flatten.wont_include Rack::ShowExceptions
   end
 
   it "include Rack::TempfileReaper in deployment environment" do
-    server = Rack::Server.new(:app => 'foo')
+    server = Rack::Server.new(app: 'foo')
     server.middleware['deployment'].flatten.must_include Rack::TempfileReaper
   end
 
   it "support CGI" do
     begin
       o, ENV["REQUEST_METHOD"] = ENV["REQUEST_METHOD"], 'foo'
-      server = Rack::Server.new(:app => 'foo')
+      server = Rack::Server.new(app: 'foo')
       server.server.name =~ /CGI/
-      Rack::Server.logging_middleware.call(server).must_equal nil
+      Rack::Server.logging_middleware.call(server).must_be_nil
     ensure
       ENV['REQUEST_METHOD'] = o
     end
   end
 
   it "be quiet if said so" do
-    server = Rack::Server.new(:app => "FOO", :quiet => true)
-    Rack::Server.logging_middleware.call(server).must_equal nil
+    server = Rack::Server.new(app: "FOO", quiet: true)
+    Rack::Server.logging_middleware.call(server).must_be_nil
   end
 
   it "use a full path to the pidfile" do
@@ -118,15 +120,15 @@ describe Rack::Server do
     pidfile = Tempfile.open('pidfile') { |f| break f }
     FileUtils.rm pidfile.path
     server = Rack::Server.new(
-      :app         => app,
-      :environment => 'none',
-      :pid         => pidfile.path,
-      :Port        => TCPServer.open('127.0.0.1', 0){|s| s.addr[1] },
-      :Host        => '127.0.0.1',
-      :Logger      => WEBrick::Log.new(nil, WEBrick::BasicLog::WARN),
-      :AccessLog   => [],
-      :daemonize   => false,
-      :server      => 'webrick'
+      app: app,
+      environment: 'none',
+      pid: pidfile.path,
+      Port: TCPServer.open('127.0.0.1', 0){|s| s.addr[1] },
+      Host: '127.0.0.1',
+      Logger: WEBrick::Log.new(nil, WEBrick::BasicLog::WARN),
+      AccessLog: [],
+      daemonize: false,
+      server: 'webrick'
     )
     t = Thread.new { server.start { |s| Thread.current[:server] = s } }
     t.join(0.01) until t[:server] && t[:server].status != :Stop
@@ -140,34 +142,36 @@ describe Rack::Server do
 
   it "check pid file presence and running process" do
     pidfile = Tempfile.open('pidfile') { |f| f.write($$); break f }.path
-    server = Rack::Server.new(:pid => pidfile)
+    server = Rack::Server.new(pid: pidfile)
     server.send(:pidfile_process_status).must_equal :running
   end
 
   it "check pid file presence and dead process" do
     dead_pid = `echo $$`.to_i
     pidfile = Tempfile.open('pidfile') { |f| f.write(dead_pid); break f }.path
-    server = Rack::Server.new(:pid => pidfile)
+    server = Rack::Server.new(pid: pidfile)
     server.send(:pidfile_process_status).must_equal :dead
   end
 
   it "check pid file presence and exited process" do
     pidfile = Tempfile.open('pidfile') { |f| break f }.path
     ::File.delete(pidfile)
-    server = Rack::Server.new(:pid => pidfile)
+    server = Rack::Server.new(pid: pidfile)
     server.send(:pidfile_process_status).must_equal :exited
   end
 
   it "check pid file presence and not owned process" do
+    owns_pid_1 = (Process.kill(0, 1) rescue nil) == 1
+    skip "cannot test if pid 1 owner matches current process (eg. docker/lxc)" if owns_pid_1
     pidfile = Tempfile.open('pidfile') { |f| f.write(1); break f }.path
-    server = Rack::Server.new(:pid => pidfile)
+    server = Rack::Server.new(pid: pidfile)
     server.send(:pidfile_process_status).must_equal :not_owned
   end
 
   it "not write pid file when it is created after check" do
     pidfile = Tempfile.open('pidfile') { |f| break f }.path
     ::File.delete(pidfile)
-    server = Rack::Server.new(:pid => pidfile)
+    server = Rack::Server.new(pid: pidfile)
     ::File.open(pidfile, 'w') { |f| f.write(1) }
     with_stderr do |err|
       lambda { server.send(:write_pid) }.must_raise SystemExit
@@ -180,7 +184,7 @@ describe Rack::Server do
 
   it "inform the user about existing pidfiles with running processes" do
     pidfile = Tempfile.open('pidfile') { |f| f.write(1); break f }.path
-    server = Rack::Server.new(:pid => pidfile)
+    server = Rack::Server.new(pid: pidfile)
     with_stderr do |err|
       lambda { server.start }.must_raise SystemExit
       err.rewind
diff --git a/test/spec_session_abstract_id.rb b/test/spec_session_abstract_id.rb
index a6568f19..3591a3de 100644
--- a/test/spec_session_abstract_id.rb
+++ b/test/spec_session_abstract_id.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 ### WARNING: there be hax in this file.
 
 require 'rack/session/abstract/id'
@@ -24,7 +26,7 @@ describe Rack::Session::Abstract::ID do
         'fake_hex'
       end
     end
-    id = Rack::Session::Abstract::ID.new nil, :secure_random => secure_random.new
+    id = Rack::Session::Abstract::ID.new nil, secure_random: secure_random.new
     id.send(:generate_sid).must_equal 'fake_hex'
   end
 
diff --git a/test/spec_session_abstract_session_hash.rb b/test/spec_session_abstract_session_hash.rb
index 6d73a80a..206e2c1b 100644
--- a/test/spec_session_abstract_session_hash.rb
+++ b/test/spec_session_abstract_session_hash.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/session/abstract/id'
 
 describe Rack::Session::Abstract::SessionHash do
@@ -8,7 +10,7 @@ describe Rack::Session::Abstract::SessionHash do
     super
     store = Class.new do
       def load_session(req)
-        ["id", {foo: :bar, baz: :qux}]
+        ["id", { foo: :bar, baz: :qux }]
       end
       def session_exists?(req)
         true
@@ -25,4 +27,21 @@ describe Rack::Session::Abstract::SessionHash do
     assert_equal [:bar, :qux], hash.values
   end
 
+  describe "#fetch" do
+    it "returns value for a matching key" do
+      assert_equal :bar, hash.fetch(:foo)
+    end
+
+    it "works with a default value" do
+      assert_equal :default, hash.fetch(:unknown, :default)
+    end
+
+    it "works with a block" do
+      assert_equal :default, hash.fetch(:unkown) { :default }
+    end
+
+    it "it raises when fetching unknown keys without defaults" do
+      lambda { hash.fetch(:unknown) }.must_raise KeyError
+    end
+  end
 end
diff --git a/test/spec_session_cookie.rb b/test/spec_session_cookie.rb
index 5b47b37e..9b4442dd 100644
--- a/test/spec_session_cookie.rb
+++ b/test/spec_session_cookie.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/session/cookie'
 require 'rack/lint'
 require 'rack/mock'
@@ -45,7 +47,7 @@ describe Rack::Session::Cookie do
     Rack::Response.new("Nothing").to_a
   end
 
-  def response_for(options={})
+  def response_for(options = {})
     request_options = options.fetch(:request, {})
     cookie = if options[:cookie].is_a?(Rack::Response)
       options[:cookie]["Set-Cookie"]
@@ -98,18 +100,18 @@ describe Rack::Session::Cookie do
 
       it 'rescues failures on decode' do
         coder = Rack::Session::Cookie::Base64::Marshal.new
-        coder.decode('lulz').must_equal nil
+        coder.decode('lulz').must_be_nil
       end
     end
 
     describe 'JSON' do
-      it 'marshals and base64 encodes' do
+      it 'JSON and base64 encodes' do
         coder = Rack::Session::Cookie::Base64::JSON.new
         obj   = %w[fuuuuu]
         coder.encode(obj).must_equal [::JSON.dump(obj)].pack('m')
       end
 
-      it 'marshals and base64 decodes' do
+      it 'JSON and base64 decodes' do
         coder = Rack::Session::Cookie::Base64::JSON.new
         str   = [::JSON.dump(%w[fuuuuu])].pack('m')
         coder.decode(str).must_equal ::JSON.parse(str.unpack('m').first)
@@ -117,7 +119,7 @@ describe Rack::Session::Cookie do
 
       it 'rescues failures on decode' do
         coder = Rack::Session::Cookie::Base64::JSON.new
-        coder.decode('lulz').must_equal nil
+        coder.decode('lulz').must_be_nil
       end
     end
 
@@ -139,7 +141,7 @@ describe Rack::Session::Cookie do
 
       it 'rescues failures on decode' do
         coder = Rack::Session::Cookie::Base64::ZipJSON.new
-        coder.decode('lulz').must_equal nil
+        coder.decode('lulz').must_be_nil
       end
     end
   end
@@ -148,22 +150,22 @@ describe Rack::Session::Cookie do
     Rack::Session::Cookie.new(incrementor)
     @warnings.first.must_match(/no secret/i)
     @warnings.clear
-    Rack::Session::Cookie.new(incrementor, :secret => 'abc')
+    Rack::Session::Cookie.new(incrementor, secret: 'abc')
     @warnings.must_be :empty?
   end
 
   it "doesn't warn if coder is configured to handle encoding" do
     Rack::Session::Cookie.new(
       incrementor,
-      :coder => Object.new,
-      :let_coder_handle_secure_encoding => true)
+      coder: Object.new,
+      let_coder_handle_secure_encoding: true)
     @warnings.must_be :empty?
   end
 
   it "still warns if coder is not set" do
     Rack::Session::Cookie.new(
       incrementor,
-      :let_coder_handle_secure_encoding => true)
+      let_coder_handle_secure_encoding: true)
     @warnings.first.must_match(/no secret/i)
   end
 
@@ -178,7 +180,7 @@ describe Rack::Session::Cookie do
       def encode(str); @calls << :encode; str; end
       def decode(str); @calls << :decode; str; end
     }.new
-    response = response_for(:app => [incrementor, { :coder => identity }])
+    response = response_for(app: [incrementor, { coder: identity }])
 
     response["Set-Cookie"].must_include "rack.session="
     response.body.must_equal '{"counter"=>1}'
@@ -186,47 +188,47 @@ describe Rack::Session::Cookie do
   end
 
   it "creates a new cookie" do
-    response = response_for(:app => incrementor)
+    response = response_for(app: incrementor)
     response["Set-Cookie"].must_include "rack.session="
     response.body.must_equal '{"counter"=>1}'
   end
 
   it "loads from a cookie" do
-    response = response_for(:app => incrementor)
+    response = response_for(app: incrementor)
 
-    response = response_for(:app => incrementor, :cookie => response)
+    response = response_for(app: incrementor, cookie: response)
     response.body.must_equal '{"counter"=>2}'
 
-    response = response_for(:app => incrementor, :cookie => response)
+    response = response_for(app: incrementor, cookie: response)
     response.body.must_equal '{"counter"=>3}'
   end
 
   it "renew session id" do
-    response = response_for(:app => incrementor)
+    response = response_for(app: incrementor)
     cookie   = response['Set-Cookie']
-    response = response_for(:app => only_session_id, :cookie => cookie)
+    response = response_for(app: only_session_id, cookie: cookie)
     cookie   = response['Set-Cookie'] if response['Set-Cookie']
 
     response.body.wont_equal ""
     old_session_id = response.body
 
-    response = response_for(:app => renewer, :cookie => cookie)
+    response = response_for(app: renewer, cookie: cookie)
     cookie   = response['Set-Cookie'] if response['Set-Cookie']
-    response = response_for(:app => only_session_id, :cookie => cookie)
+    response = response_for(app: only_session_id, cookie: cookie)
 
     response.body.wont_equal ""
     response.body.wont_equal old_session_id
   end
 
   it "destroys session" do
-    response = response_for(:app => incrementor)
-    response = response_for(:app => only_session_id, :cookie => response)
+    response = response_for(app: incrementor)
+    response = response_for(app: only_session_id, cookie: response)
 
     response.body.wont_equal ""
     old_session_id = response.body
 
-    response = response_for(:app => destroy_session, :cookie => response)
-    response = response_for(:app => only_session_id, :cookie => response)
+    response = response_for(app: destroy_session, cookie: response)
+    response = response_for(app: only_session_id, cookie: response)
 
     response.body.wont_equal ""
     response.body.wont_equal old_session_id
@@ -234,104 +236,104 @@ describe Rack::Session::Cookie do
 
   it "survives broken cookies" do
     response = response_for(
-      :app => incrementor,
-      :cookie => "rack.session=blarghfasel"
+      app: incrementor,
+      cookie: "rack.session=blarghfasel"
     )
     response.body.must_equal '{"counter"=>1}'
 
     response = response_for(
-      :app => [incrementor, { :secret => "test" }],
-      :cookie => "rack.session="
+      app: [incrementor, { secret: "test" }],
+      cookie: "rack.session="
     )
     response.body.must_equal '{"counter"=>1}'
   end
 
   it "barks on too big cookies" do
     lambda{
-      response_for(:app => bigcookie, :request => { :fatal => true })
+      response_for(app: bigcookie, request: { fatal: true })
     }.must_raise Rack::MockRequest::FatalWarning
   end
 
   it "loads from a cookie with integrity hash" do
-    app = [incrementor, { :secret => "test" }]
+    app = [incrementor, { secret: "test" }]
 
-    response = response_for(:app => app)
-    response = response_for(:app => app, :cookie => response)
+    response = response_for(app: app)
+    response = response_for(app: app, cookie: response)
     response.body.must_equal '{"counter"=>2}'
 
-    response = response_for(:app => app, :cookie => response)
+    response = response_for(app: app, cookie: response)
     response.body.must_equal '{"counter"=>3}'
 
-    app = [incrementor, { :secret => "other" }]
+    app = [incrementor, { secret: "other" }]
 
-    response = response_for(:app => app, :cookie => response)
+    response = response_for(app: app, cookie: response)
     response.body.must_equal '{"counter"=>1}'
   end
 
   it "loads from a cookie with accept-only integrity hash for graceful key rotation" do
-    response = response_for(:app => [incrementor, { :secret => "test" }])
+    response = response_for(app: [incrementor, { secret: "test" }])
 
-    app = [incrementor, { :secret => "test2", :old_secret => "test" }]
-    response = response_for(:app => app, :cookie => response)
+    app = [incrementor, { secret: "test2", old_secret: "test" }]
+    response = response_for(app: app, cookie: response)
     response.body.must_equal '{"counter"=>2}'
 
-    app = [incrementor, { :secret => "test3", :old_secret => "test2" }]
-    response = response_for(:app => app, :cookie => response)
+    app = [incrementor, { secret: "test3", old_secret: "test2" }]
+    response = response_for(app: app, cookie: response)
     response.body.must_equal '{"counter"=>3}'
   end
 
   it "ignores tampered with session cookies" do
-    app = [incrementor, { :secret => "test" }]
-    response = response_for(:app => app)
+    app = [incrementor, { secret: "test" }]
+    response = response_for(app: app)
     response.body.must_equal '{"counter"=>1}'
 
-    response = response_for(:app => app, :cookie => response)
+    response = response_for(app: app, cookie: response)
     response.body.must_equal '{"counter"=>2}'
 
     _, digest = response["Set-Cookie"].split("--")
     tampered_with_cookie = "hackerman-was-here" + "--" + digest
 
-    response = response_for(:app => app, :cookie => tampered_with_cookie)
+    response = response_for(app: app, cookie: tampered_with_cookie)
     response.body.must_equal '{"counter"=>1}'
   end
 
   it "supports either of secret or old_secret" do
-    app = [incrementor, { :secret => "test" }]
-    response = response_for(:app => app)
+    app = [incrementor, { secret: "test" }]
+    response = response_for(app: app)
     response.body.must_equal '{"counter"=>1}'
 
-    response = response_for(:app => app, :cookie => response)
+    response = response_for(app: app, cookie: response)
     response.body.must_equal '{"counter"=>2}'
 
-    app = [incrementor, { :old_secret => "test" }]
-    response = response_for(:app => app)
+    app = [incrementor, { old_secret: "test" }]
+    response = response_for(app: app)
     response.body.must_equal '{"counter"=>1}'
 
-    response = response_for(:app => app, :cookie => response)
+    response = response_for(app: app, cookie: response)
     response.body.must_equal '{"counter"=>2}'
   end
 
   it "supports custom digest class" do
-    app = [incrementor, { :secret => "test", hmac: OpenSSL::Digest::SHA256 }]
+    app = [incrementor, { secret: "test", hmac: OpenSSL::Digest::SHA256 }]
 
-    response = response_for(:app => app)
-    response = response_for(:app => app, :cookie => response)
+    response = response_for(app: app)
+    response = response_for(app: app, cookie: response)
     response.body.must_equal '{"counter"=>2}'
 
-    response = response_for(:app => app, :cookie => response)
+    response = response_for(app: app, cookie: response)
     response.body.must_equal '{"counter"=>3}'
 
-    app = [incrementor, { :secret => "other" }]
+    app = [incrementor, { secret: "other" }]
 
-    response = response_for(:app => app, :cookie => response)
+    response = response_for(app: app, cookie: response)
     response.body.must_equal '{"counter"=>1}'
   end
 
   it "can handle Rack::Lint middleware" do
-    response = response_for(:app => incrementor)
+    response = response_for(app: incrementor)
 
     lint = Rack::Lint.new(session_id)
-    response = response_for(:app => lint, :cookie => response)
+    response = response_for(app: lint, cookie: response)
     response.body.wont_be :nil?
   end
 
@@ -346,75 +348,75 @@ describe Rack::Session::Cookie do
       end
     end
 
-    response = response_for(:app => incrementor)
+    response = response_for(app: incrementor)
 
     inspector = TestEnvInspector.new(session_id)
-    response = response_for(:app => inspector, :cookie => response)
+    response = response_for(app: inspector, cookie: response)
     response.body.wont_be :nil?
   end
 
   it "returns the session id in the session hash" do
-    response = response_for(:app => incrementor)
+    response = response_for(app: incrementor)
     response.body.must_equal '{"counter"=>1}'
 
-    response = response_for(:app => session_id, :cookie => response)
+    response = response_for(app: session_id, cookie: response)
     response.body.must_match(/"session_id"=>/)
     response.body.must_match(/"counter"=>1/)
   end
 
   it "does not return a cookie if set to secure but not using ssl" do
-    app = [incrementor, { :secure => true }]
+    app = [incrementor, { secure: true }]
 
-    response = response_for(:app => app)
+    response = response_for(app: app)
     response["Set-Cookie"].must_be_nil
 
-    response = response_for(:app => app, :request => { "HTTPS" => "on" })
+    response = response_for(app: app, request: { "HTTPS" => "on" })
     response["Set-Cookie"].wont_be :nil?
     response["Set-Cookie"].must_match(/secure/)
   end
 
   it "does not return a cookie if cookie was not read/written" do
-    response = response_for(:app => nothing)
+    response = response_for(app: nothing)
     response["Set-Cookie"].must_be_nil
   end
 
   it "does not return a cookie if cookie was not written (only read)" do
-    response = response_for(:app => session_id)
+    response = response_for(app: session_id)
     response["Set-Cookie"].must_be_nil
   end
 
   it "returns even if not read/written if :expire_after is set" do
-    app = [nothing, { :expire_after => 3600 }]
-    request = { "rack.session" => { "not" => "empty" }}
-    response = response_for(:app => app, :request => request)
+    app = [nothing, { expire_after: 3600 }]
+    request = { "rack.session" => { "not" => "empty" } }
+    response = response_for(app: app, request: request)
     response["Set-Cookie"].wont_be :nil?
   end
 
   it "returns no cookie if no data was written and no session was created previously, even if :expire_after is set" do
-    app = [nothing, { :expire_after => 3600 }]
-    response = response_for(:app => app)
+    app = [nothing, { expire_after: 3600 }]
+    response = response_for(app: app)
     response["Set-Cookie"].must_be_nil
   end
 
   it "exposes :secret in env['rack.session.option']" do
-    response = response_for(:app => [session_option[:secret], { :secret => "foo" }])
+    response = response_for(app: [session_option[:secret], { secret: "foo" }])
     response.body.must_equal '"foo"'
   end
 
   it "exposes :coder in env['rack.session.option']" do
-    response = response_for(:app => session_option[:coder])
+    response = response_for(app: session_option[:coder])
     response.body.must_match(/Base64::Marshal/)
   end
 
   it "allows passing in a hash with session data from middleware in front" do
-    request = { 'rack.session' => { :foo => 'bar' }}
-    response = response_for(:app => session_id, :request => request)
+    request = { 'rack.session' => { foo: 'bar' } }
+    response = response_for(app: session_id, request: request)
     response.body.must_match(/foo/)
   end
 
   it "allows modifying session data with session data from middleware in front" do
-    request = { 'rack.session' => { :foo => 'bar' }}
-    response = response_for(:app => incrementor, :request => request)
+    request = { 'rack.session' => { foo: 'bar' } }
+    response = response_for(app: incrementor, request: request)
     response.body.must_match(/counter/)
     response.body.must_match(/foo/)
   end
@@ -423,7 +425,7 @@ describe Rack::Session::Cookie do
     @counter = 0
     app = lambda do |env|
       env["rack.session"]["message"] ||= ""
-      env["rack.session"]["message"] << "#{(@counter += 1).to_s}--"
+      env["rack.session"]["message"] += "#{(@counter += 1).to_s}--"
       hash = env["rack.session"].dup
       hash.delete("session_id")
       Rack::Response.new(hash["message"]).to_a
@@ -433,10 +435,10 @@ describe Rack::Session::Cookie do
       def encode(hash); hash.inspect end
       def decode(str); eval(str) if str; end
     }.new
-    _app = [ app, { :secret => "test", :coder => unsafe_coder } ]
-    response = response_for(:app => _app)
+    _app = [ app, { secret: "test", coder: unsafe_coder } ]
+    response = response_for(app: _app)
     response.body.must_equal "1--"
-    response = response_for(:app => _app, :cookie => response)
+    response = response_for(app: _app, cookie: response)
     response.body.must_equal "1--2--"
   end
 end
diff --git a/test/spec_session_memcache.rb b/test/spec_session_memcache.rb
index 66903d69..a015cee6 100644
--- a/test/spec_session_memcache.rb
+++ b/test/spec_session_memcache.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 begin
   require 'rack/session/memcache'
   require 'rack/lint'
@@ -36,17 +38,17 @@ begin
 
     it "faults on no connection" do
       lambda {
-        Rack::Session::Memcache.new(incrementor, :memcache_server => 'nosuchserver')
+        Rack::Session::Memcache.new(incrementor, memcache_server: 'nosuchserver')
       }.must_raise(RuntimeError).message.must_equal 'No memcache servers'
     end
 
     it "connects to existing server" do
-      test_pool = MemCache.new(incrementor, :namespace => 'test:rack:session')
+      test_pool = MemCache.new(incrementor, namespace: 'test:rack:session')
       test_pool.namespace.must_equal 'test:rack:session'
     end
 
     it "passes options to MemCache" do
-      pool = Rack::Session::Memcache.new(incrementor, :namespace => 'test:rack:session')
+      pool = Rack::Session::Memcache.new(incrementor, namespace: 'test:rack:session')
       pool.pool.namespace.must_equal 'test:rack:session'
     end
 
@@ -80,7 +82,7 @@ begin
     end
 
     it "determines session from params" do
-      pool = Rack::Session::Memcache.new(incrementor, :cookie_only => false)
+      pool = Rack::Session::Memcache.new(incrementor, cookie_only: false)
       req = Rack::MockRequest.new(pool)
       res = req.get("/")
       sid = res["Set-Cookie"][session_match, 1]
@@ -101,7 +103,7 @@ begin
     end
 
     it "maintains freshness" do
-      pool = Rack::Session::Memcache.new(incrementor, :expire_after => 3)
+      pool = Rack::Session::Memcache.new(incrementor, expire_after: 3)
       res = Rack::MockRequest.new(pool).get('/')
       res.body.must_include '"counter"=>1'
       cookie = res["Set-Cookie"]
@@ -143,7 +145,7 @@ begin
       res1.body.must_equal '{"counter"=>1}'
 
       res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
-      res2["Set-Cookie"].must_equal nil
+      res2["Set-Cookie"].must_be_nil
       res2.body.must_equal '{"counter"=>2}'
 
       res3 = req.get("/", "HTTP_COOKIE" => cookie)
@@ -183,7 +185,7 @@ begin
       creq = Rack::MockRequest.new(count)
 
       res0 = dreq.get("/")
-      res0["Set-Cookie"].must_equal nil
+      res0["Set-Cookie"].must_be_nil
       res0.body.must_equal '{"counter"=>1}'
 
       res0 = creq.get("/")
@@ -201,7 +203,7 @@ begin
       creq = Rack::MockRequest.new(count)
 
       res0 = sreq.get("/")
-      res0["Set-Cookie"].must_equal nil
+      res0["Set-Cookie"].must_be_nil
       res0.body.must_equal '{"counter"=>1}'
 
       res0 = creq.get("/")
@@ -215,8 +217,8 @@ begin
       hash_check = proc do |env|
         session = env['rack.session']
         unless session.include? 'test'
-          session.update :a => :b, :c => { :d => :e },
-            :f => { :g => { :h => :i} }, 'test' => true
+          session.update :a => :b, :c => { d: :e },
+            :f => { g: { h: :i } }, 'test' => true
         else
           session[:f][:g][:h] = :j
         end
@@ -252,12 +254,12 @@ begin
         # emulate disconjoinment of threading
         env['rack.session'] = env['rack.session'].dup
         Thread.stop
-        env['rack.session'][(Time.now.usec*rand).to_i] = true
+        env['rack.session'][(Time.now.usec * rand).to_i] = true
         incrementor.call(env)
       end
       tses = Rack::Utils::Context.new pool, delta_incrementor
       treq = Rack::MockRequest.new(tses)
-      tnum = rand(7).to_i+5
+      tnum = rand(7).to_i + 5
       r = Array.new(tnum) do
         Thread.new(treq) do |run|
           run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
@@ -269,10 +271,10 @@ begin
       end
 
       session = pool.pool.get(session_id)
-      session.size.must_equal tnum+1 # counter
+      session.size.must_equal tnum + 1 # counter
       session['counter'].must_equal 2 # meeeh
 
-      tnum = rand(7).to_i+5
+      tnum = rand(7).to_i + 5
       r = Array.new(tnum) do
         app = Rack::Utils::Context.new pool, time_delta
         req = Rack::MockRequest.new app
@@ -286,17 +288,17 @@ begin
       end
 
       session = pool.pool.get(session_id)
-      session.size.must_equal tnum+1
+      session.size.must_equal tnum + 1
       session['counter'].must_equal 3
 
       drop_counter = proc do |env|
         env['rack.session'].delete 'counter'
         env['rack.session']['foo'] = 'bar'
-        [200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
+        [200, { 'Content-Type' => 'text/plain' }, env['rack.session'].inspect]
       end
       tses = Rack::Utils::Context.new pool, drop_counter
       treq = Rack::MockRequest.new(tses)
-      tnum = rand(7).to_i+5
+      tnum = rand(7).to_i + 5
       r = Array.new(tnum) do
         Thread.new(treq) do |run|
           run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
@@ -308,7 +310,7 @@ begin
       end
 
       session = pool.pool.get(session_id)
-      session.size.must_equal r.size+1
+      session.size.must_equal r.size + 1
       session['counter'].must_be_nil?
       session['foo'].must_equal 'bar'
     end
diff --git a/test/spec_session_pool.rb b/test/spec_session_pool.rb
index 5eaadfff..fda5f56e 100644
--- a/test/spec_session_pool.rb
+++ b/test/spec_session_pool.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'thread'
 require 'rack/lint'
 require 'rack/mock'
@@ -138,7 +140,7 @@ describe Rack::Session::Pool do
     dreq = Rack::MockRequest.new(defer)
 
     res1 = dreq.get("/")
-    res1["Set-Cookie"].must_equal nil
+    res1["Set-Cookie"].must_be_nil
     res1.body.must_equal '{"counter"=>1}'
     pool.pool.size.must_equal 1
   end
@@ -157,18 +159,18 @@ describe Rack::Session::Pool do
     res = req.get('/')
     res.body.must_equal '{"counter"=>1}'
     cookie = res["Set-Cookie"]
-    sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
+    sess_id = cookie[/#{pool.key}=([^,;]+)/, 1]
 
     delta_incrementor = lambda do |env|
       # emulate disconjoinment of threading
       env['rack.session'] = env['rack.session'].dup
       Thread.stop
-      env['rack.session'][(Time.now.usec*rand).to_i] = true
+      env['rack.session'][(Time.now.usec * rand).to_i] = true
       incrementor.call(env)
     end
     tses = Rack::Utils::Context.new pool, delta_incrementor
     treq = Rack::MockRequest.new(tses)
-    tnum = rand(7).to_i+5
+    tnum = rand(7).to_i + 5
     r = Array.new(tnum) do
       Thread.new(treq) do |run|
         run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
@@ -180,7 +182,7 @@ describe Rack::Session::Pool do
     end
 
     session = pool.pool[sess_id]
-    session.size.must_equal tnum+1 # counter
+    session.size.must_equal tnum + 1 # counter
     session['counter'].must_equal 2 # meeeh
   end
 
@@ -197,13 +199,13 @@ describe Rack::Session::Pool do
   end
 
   it "returns even if not read/written if :expire_after is set" do
-    app = Rack::Session::Pool.new(nothing, :expire_after => 3600)
-    res = Rack::MockRequest.new(app).get("/", 'rack.session' => {'not' => 'empty'})
+    app = Rack::Session::Pool.new(nothing, expire_after: 3600)
+    res = Rack::MockRequest.new(app).get("/", 'rack.session' => { 'not' => 'empty' })
     res["Set-Cookie"].wont_be :nil?
   end
 
   it "returns no cookie if no data was written and no session was created previously, even if :expire_after is set" do
-    app = Rack::Session::Pool.new(nothing, :expire_after => 3600)
+    app = Rack::Session::Pool.new(nothing, expire_after: 3600)
     res = Rack::MockRequest.new(app).get("/")
     res["Set-Cookie"].must_be_nil
   end
diff --git a/test/spec_show_exceptions.rb b/test/spec_show_exceptions.rb
index cd44c816..a4ade121 100644
--- a/test/spec_show_exceptions.rb
+++ b/test/spec_show_exceptions.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/show_exceptions'
 require 'rack/lint'
 require 'rack/mock'
@@ -25,6 +27,24 @@ describe Rack::ShowExceptions do
     assert_match(res, /ShowExceptions/)
   end
 
+  it "works with binary data in the Rack environment" do
+    res = nil
+
+    # "\xCC" is not a valid UTF-8 string
+    req = Rack::MockRequest.new(
+      show_exceptions(
+        lambda{|env| env['foo'] = "\xCC"; raise RuntimeError }
+    ))
+
+    res = req.get("/", "HTTP_ACCEPT" => "text/html")
+
+    res.must_be :server_error?
+    res.status.must_equal 500
+
+    assert_match(res, /RuntimeError/)
+    assert_match(res, /ShowExceptions/)
+  end
+
   it "responds with HTML only to requests accepting HTML" do
     res = nil
 
@@ -35,11 +55,11 @@ describe Rack::ShowExceptions do
 
     [
       # Serve text/html when the client accepts text/html
-      ["text/html", ["/", {"HTTP_ACCEPT" => "text/html"}]],
-      ["text/html", ["/", {"HTTP_ACCEPT" => "*/*"}]],
+      ["text/html", ["/", { "HTTP_ACCEPT" => "text/html" }]],
+      ["text/html", ["/", { "HTTP_ACCEPT" => "*/*" }]],
       # Serve text/plain when the client does not accept text/html
       ["text/plain", ["/"]],
-      ["text/plain", ["/", {"HTTP_ACCEPT" => "application/json"}]]
+      ["text/plain", ["/", { "HTTP_ACCEPT" => "application/json" }]]
     ].each do |exmime, rargs|
       res = req.get(*rargs)
 
@@ -77,4 +97,39 @@ describe Rack::ShowExceptions do
     assert_match(res, /ShowExceptions/)
     assert_match(res, /unknown location/)
   end
+
+  it "allows subclasses to override template" do
+    c = Class.new(Rack::ShowExceptions) do
+      TEMPLATE = ERB.new("foo")
+
+      def template
+        TEMPLATE
+      end
+    end
+
+    app = lambda { |env| raise RuntimeError, "", [] }
+
+    req = Rack::MockRequest.new(
+      Rack::Lint.new c.new(app)
+    )
+
+    res = req.get("/", "HTTP_ACCEPT" => "text/html")
+
+    res.must_be :server_error?
+    res.status.must_equal 500
+    res.body.must_equal "foo"
+  end
+
+  it "knows to prefer plaintext for non-html" do
+    # We don't need an app for this
+    exc = Rack::ShowExceptions.new(nil)
+
+    [
+      [{ "HTTP_ACCEPT" => "text/plain" }, true],
+      [{ "HTTP_ACCEPT" => "text/foo" }, true],
+      [{ "HTTP_ACCEPT" => "text/html" }, false]
+    ].each do |env, expected|
+      assert_equal(expected, exc.prefers_plaintext?(env))
+    end
+  end
 end
diff --git a/test/spec_show_status.rb b/test/spec_show_status.rb
index d32dc7cb..ca23134e 100644
--- a/test/spec_show_status.rb
+++ b/test/spec_show_status.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/show_status'
 require 'rack/lint'
 require 'rack/mock'
@@ -12,10 +14,10 @@ describe Rack::ShowStatus do
   it "provide a default status message" do
     req = Rack::MockRequest.new(
       show_status(lambda{|env|
-        [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []]
+        [404, { "Content-Type" => "text/plain", "Content-Length" => "0" }, []]
     }))
 
-    res = req.get("/", :lint => true)
+    res = req.get("/", lint: true)
     res.must_be :not_found?
     res.wont_be_empty
 
@@ -29,10 +31,10 @@ describe Rack::ShowStatus do
       show_status(
         lambda{|env|
           env["rack.showstatus.detail"] = "gone too meta."
-          [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []]
+          [404, { "Content-Type" => "text/plain", "Content-Length" => "0" }, []]
     }))
 
-    res = req.get("/", :lint => true)
+    res = req.get("/", lint: true)
     res.must_be :not_found?
     res.wont_be_empty
 
@@ -48,10 +50,10 @@ describe Rack::ShowStatus do
       show_status(
         lambda{|env|
           env["rack.showstatus.detail"] = detail
-          [500, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []]
+          [500, { "Content-Type" => "text/plain", "Content-Length" => "0" }, []]
     }))
 
-    res = req.get("/", :lint => true)
+    res = req.get("/", lint: true)
     res.wont_be_empty
 
     res["Content-Type"].must_equal "text/html"
@@ -64,21 +66,21 @@ describe Rack::ShowStatus do
     req = Rack::MockRequest.new(
       show_status(
         lambda{|env|
-          [404, {"Content-Type" => "text/plain", "Content-Length" => "4"}, ["foo!"]]
+          [404, { "Content-Type" => "text/plain", "Content-Length" => "4" }, ["foo!"]]
     }))
 
-    res = req.get("/", :lint => true)
+    res = req.get("/", lint: true)
     res.must_be :not_found?
 
     res.body.must_equal "foo!"
   end
 
   it "pass on original headers" do
-    headers = {"WWW-Authenticate" => "Basic blah"}
+    headers = { "WWW-Authenticate" => "Basic blah" }
 
     req = Rack::MockRequest.new(
       show_status(lambda{|env| [401, headers, []] }))
-    res = req.get("/", :lint => true)
+    res = req.get("/", lint: true)
 
     res["WWW-Authenticate"].must_equal "Basic blah"
   end
@@ -88,10 +90,10 @@ describe Rack::ShowStatus do
       show_status(
         lambda{|env|
           env["rack.showstatus.detail"] = "gone too meta."
-          [404, {"Content-Type" => "text/plain", "Content-Length" => "4"}, ["foo!"]]
+          [404, { "Content-Type" => "text/plain", "Content-Length" => "4" }, ["foo!"]]
     }))
 
-    res = req.get("/", :lint => true)
+    res = req.get("/", lint: true)
     res.must_be :not_found?
     res.wont_be_empty
 
diff --git a/test/spec_static.rb b/test/spec_static.rb
index f0a47171..d33e8edc 100644
--- a/test/spec_static.rb
+++ b/test/spec_static.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/static'
 require 'rack/lint'
 require 'rack/mock'
@@ -7,7 +9,7 @@ require 'stringio'
 
 class DummyApp
   def call(env)
-    [200, {"Content-Type" => "text/plain"}, ["Hello World"]]
+    [200, { "Content-Type" => "text/plain" }, ["Hello World"]]
   end
 end
 
@@ -18,15 +20,17 @@ describe Rack::Static do
 
   root = File.expand_path(File.dirname(__FILE__))
 
-  OPTIONS = {:urls => ["/cgi"], :root => root}
-  STATIC_OPTIONS = {:urls => [""], :root => "#{root}/static", :index => 'index.html'}
-  HASH_OPTIONS = {:urls => {"/cgi/sekret" => 'cgi/test'}, :root => root}
-  HASH_ROOT_OPTIONS = {:urls => {"/" => "static/foo.html"}, :root => root}
-  GZIP_OPTIONS = {:urls => ["/cgi"], :root => root, :gzip=>true}
+  OPTIONS = { urls: ["/cgi"], root: root }
+  STATIC_OPTIONS = { urls: [""], root: "#{root}/static", index: 'index.html' }
+  STATIC_URLS_OPTIONS = { urls: ["/static"], root: "#{root}", index: 'index.html' }
+  HASH_OPTIONS = { urls: { "/cgi/sekret" => 'cgi/test' }, root: root }
+  HASH_ROOT_OPTIONS = { urls: { "/" => "static/foo.html" }, root: root }
+  GZIP_OPTIONS = { urls: ["/cgi"], root: root, gzip: true }
 
   before do
   @request = Rack::MockRequest.new(static(DummyApp.new, OPTIONS))
   @static_request = Rack::MockRequest.new(static(DummyApp.new, STATIC_OPTIONS))
+  @static_urls_request = Rack::MockRequest.new(static(DummyApp.new, STATIC_URLS_OPTIONS))
   @hash_request = Rack::MockRequest.new(static(DummyApp.new, HASH_OPTIONS))
   @hash_root_request = Rack::MockRequest.new(static(DummyApp.new, HASH_ROOT_OPTIONS))
   @gzip_request = Rack::MockRequest.new(static(DummyApp.new, GZIP_OPTIONS))
@@ -63,6 +67,16 @@ describe Rack::Static do
     res.body.must_match(/another index!/)
   end
 
+  it "does not call index file when requesting folder with unknown prefix" do
+    res = @static_urls_request.get("/static/another/")
+    res.must_be :ok?
+    res.body.must_match(/index!/)
+
+    res = @static_urls_request.get("/something/else/")
+    res.must_be :ok?
+    res.body.must_equal "Hello World"
+  end
+
   it "doesn't call index file if :index option was omitted" do
     res = @request.get("/")
     res.body.must_equal "Hello World"
@@ -87,7 +101,7 @@ describe Rack::Static do
   end
 
   it "serves gzipped files if client accepts gzip encoding and gzip files are present" do
-    res = @gzip_request.get("/cgi/test", 'HTTP_ACCEPT_ENCODING'=>'deflate, gzip')
+    res = @gzip_request.get("/cgi/test", 'HTTP_ACCEPT_ENCODING' => 'deflate, gzip')
     res.must_be :ok?
     res.headers['Content-Encoding'].must_equal 'gzip'
     res.headers['Content-Type'].must_equal 'text/plain'
@@ -95,9 +109,9 @@ describe Rack::Static do
   end
 
   it "serves regular files if client accepts gzip encoding and gzip files are not present" do
-    res = @gzip_request.get("/cgi/rackup_stub.rb", 'HTTP_ACCEPT_ENCODING'=>'deflate, gzip')
+    res = @gzip_request.get("/cgi/rackup_stub.rb", 'HTTP_ACCEPT_ENCODING' => 'deflate, gzip')
     res.must_be :ok?
-    res.headers['Content-Encoding'].must_equal nil
+    res.headers['Content-Encoding'].must_be_nil
     res.headers['Content-Type'].must_equal 'text/x-script.ruby'
     res.body.must_match(/ruby/)
   end
@@ -105,27 +119,27 @@ describe Rack::Static do
   it "serves regular files if client does not accept gzip encoding" do
     res = @gzip_request.get("/cgi/test")
     res.must_be :ok?
-    res.headers['Content-Encoding'].must_equal nil
+    res.headers['Content-Encoding'].must_be_nil
     res.headers['Content-Type'].must_equal 'text/plain'
     res.body.must_match(/ruby/)
   end
 
   it "supports serving fixed cache-control (legacy option)" do
-    opts = OPTIONS.merge(:cache_control => 'public')
+    opts = OPTIONS.merge(cache_control: 'public')
     request = Rack::MockRequest.new(static(DummyApp.new, opts))
     res = request.get("/cgi/test")
     res.must_be :ok?
     res.headers['Cache-Control'].must_equal 'public'
   end
 
-  HEADER_OPTIONS = {:urls => ["/cgi"], :root => root, :header_rules => [
-    [:all, {'Cache-Control' => 'public, max-age=100'}],
-    [:fonts, {'Cache-Control' => 'public, max-age=200'}],
-    [%w(png jpg), {'Cache-Control' => 'public, max-age=300'}],
-    ['/cgi/assets/folder/', {'Cache-Control' => 'public, max-age=400'}],
-    ['cgi/assets/javascripts', {'Cache-Control' => 'public, max-age=500'}],
-    [/\.(css|erb)\z/, {'Cache-Control' => 'public, max-age=600'}]
-  ]}
+  HEADER_OPTIONS = { urls: ["/cgi"], root: root, header_rules: [
+    [:all, { 'Cache-Control' => 'public, max-age=100' }],
+    [:fonts, { 'Cache-Control' => 'public, max-age=200' }],
+    [%w(png jpg), { 'Cache-Control' => 'public, max-age=300' }],
+    ['/cgi/assets/folder/', { 'Cache-Control' => 'public, max-age=400' }],
+    ['cgi/assets/javascripts', { 'Cache-Control' => 'public, max-age=500' }],
+    [/\.(css|erb)\z/, { 'Cache-Control' => 'public, max-age=600' }]
+  ] }
 
   it "supports header rule :all" do
     # Headers for all files via :all shortcut
@@ -170,9 +184,9 @@ describe Rack::Static do
 
   it "prioritizes header rules over fixed cache-control setting (legacy option)" do
     opts = OPTIONS.merge(
-      :cache_control => 'public, max-age=24',
-      :header_rules => [
-        [:all, {'Cache-Control' => 'public, max-age=42'}]
+      cache_control: 'public, max-age=24',
+      header_rules: [
+        [:all, { 'Cache-Control' => 'public, max-age=42' }]
       ])
 
     request = Rack::MockRequest.new(static(DummyApp.new, opts))
@@ -181,4 +195,14 @@ describe Rack::Static do
     res.headers['Cache-Control'].must_equal 'public, max-age=42'
   end
 
+  it "expands the root path upon the middleware initialization" do
+    relative_path = STATIC_OPTIONS[:root].sub("#{Dir.pwd}/", '')
+    opts = { urls: [""], root: relative_path, index: 'index.html' }
+    request = Rack::MockRequest.new(static(DummyApp.new, opts))
+    Dir.chdir '..' do
+      res = request.get("")
+      res.must_be :ok?
+      res.body.must_match(/index!/)
+    end
+  end
 end
diff --git a/test/spec_tempfile_reaper.rb b/test/spec_tempfile_reaper.rb
index b7c62563..0e7de841 100644
--- a/test/spec_tempfile_reaper.rb
+++ b/test/spec_tempfile_reaper.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/tempfile_reaper'
 require 'rack/lint'
 require 'rack/mock'
diff --git a/test/spec_thin.rb b/test/spec_thin.rb
index 85b225ed..0729c3f3 100644
--- a/test/spec_thin.rb
+++ b/test/spec_thin.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 begin
 require 'rack/handler/thin'
 require File.expand_path('../testrequest', __FILE__)
@@ -13,7 +15,7 @@ describe Rack::Handler::Thin do
     Thin::Logging.silent = true
 
     @thread = Thread.new do
-      Rack::Handler::Thin.run(@app, :Host => @host='127.0.0.1', :Port => @port=9204, :tag => "tag") do |server|
+      Rack::Handler::Thin.run(@app, Host: @host = '127.0.0.1', Port: @port = 9204, tag: "tag") do |server|
         @server = server
       end
     end
@@ -44,7 +46,7 @@ describe Rack::Handler::Thin do
 
   it "have rack headers" do
     GET("/")
-    response["rack.version"].must_equal [1,0]
+    response["rack.version"].must_equal [1, 0]
     response["rack.multithread"].must_equal false
     response["rack.multiprocess"].must_equal false
     response["rack.run_once"].must_equal false
@@ -66,7 +68,7 @@ describe Rack::Handler::Thin do
   end
 
   it "have CGI headers on POST" do
-    POST("/", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
+    POST("/", { "rack-form-data" => "23" }, { 'X-test-header' => '42' })
     status.must_equal 200
     response["REQUEST_METHOD"].must_equal "POST"
     response["REQUEST_PATH"].must_equal "/"
@@ -76,7 +78,7 @@ describe Rack::Handler::Thin do
   end
 
   it "support HTTP auth" do
-    GET("/test", {:user => "ruth", :passwd => "secret"})
+    GET("/test", { user: "ruth", passwd: "secret" })
     response["HTTP_AUTHORIZATION"].must_equal "Basic cnV0aDpzZWNyZXQ="
   end
 
diff --git a/test/spec_urlmap.rb b/test/spec_urlmap.rb
index 9d655c22..9ce38298 100644
--- a/test/spec_urlmap.rb
+++ b/test/spec_urlmap.rb
@@ -1,4 +1,6 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/urlmap'
 require 'rack/mock'
 
@@ -117,6 +119,14 @@ describe Rack::URLMap do
     res.must_be :ok?
     res["X-Position"].must_equal "default.org"
 
+    res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "any-host.org")
+    res.must_be :ok?
+    res["X-Position"].must_equal "default.org"
+
+    res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "any-host.org", "HTTP_X_FORWARDED_HOST" => "any-host.org")
+    res.must_be :ok?
+    res["X-Position"].must_equal "default.org"
+
     res = Rack::MockRequest.new(map).get("/",
                                          "HTTP_HOST" => "example.org:9292",
                                          "SERVER_PORT" => "9292")
@@ -127,7 +137,7 @@ describe Rack::URLMap do
   it "be nestable" do
     map = Rack::Lint.new(Rack::URLMap.new("/foo" =>
       Rack::URLMap.new("/bar" =>
-        Rack::URLMap.new("/quux" =>  lambda { |env|
+        Rack::URLMap.new("/quux" => lambda { |env|
                            [200,
                             { "Content-Type" => "text/plain",
                               "X-Position" => "/foo/bar/quux",
diff --git a/test/spec_utils.rb b/test/spec_utils.rb
index b24762c9..6210fd73 100644
--- a/test/spec_utils.rb
+++ b/test/spec_utils.rb
@@ -1,5 +1,6 @@
-# -*- encoding: utf-8 -*-
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/utils'
 require 'rack/mock'
 require 'timeout'
@@ -72,7 +73,7 @@ describe Rack::Utils do
   end
 
   it "escape path spaces with %20" do
-    Rack::Utils.escape_path("foo bar").must_equal  "foo%20bar"
+    Rack::Utils.escape_path("foo bar").must_equal "foo%20bar"
   end
 
   it "unescape correctly" do
@@ -181,38 +182,38 @@ describe Rack::Utils do
       must_equal "foo" => ["bar"], "baz" => ["1", "2", "3"]
 
     Rack::Utils.parse_nested_query("x[y][z]=1").
-      must_equal "x" => {"y" => {"z" => "1"}}
+      must_equal "x" => { "y" => { "z" => "1" } }
     Rack::Utils.parse_nested_query("x[y][z][]=1").
-      must_equal "x" => {"y" => {"z" => ["1"]}}
+      must_equal "x" => { "y" => { "z" => ["1"] } }
     Rack::Utils.parse_nested_query("x[y][z]=1&x[y][z]=2").
-      must_equal "x" => {"y" => {"z" => "2"}}
+      must_equal "x" => { "y" => { "z" => "2" } }
     Rack::Utils.parse_nested_query("x[y][z][]=1&x[y][z][]=2").
-      must_equal "x" => {"y" => {"z" => ["1", "2"]}}
+      must_equal "x" => { "y" => { "z" => ["1", "2"] } }
 
     Rack::Utils.parse_nested_query("x[y][][z]=1").
-      must_equal "x" => {"y" => [{"z" => "1"}]}
+      must_equal "x" => { "y" => [{ "z" => "1" }] }
     Rack::Utils.parse_nested_query("x[y][][z][]=1").
-      must_equal "x" => {"y" => [{"z" => ["1"]}]}
+      must_equal "x" => { "y" => [{ "z" => ["1"] }] }
     Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=2").
-      must_equal "x" => {"y" => [{"z" => "1", "w" => "2"}]}
+      must_equal "x" => { "y" => [{ "z" => "1", "w" => "2" }] }
 
     Rack::Utils.parse_nested_query("x[y][][v][w]=1").
-      must_equal "x" => {"y" => [{"v" => {"w" => "1"}}]}
+      must_equal "x" => { "y" => [{ "v" => { "w" => "1" } }] }
     Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][v][w]=2").
-      must_equal "x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}
+      must_equal "x" => { "y" => [{ "z" => "1", "v" => { "w" => "2" } }] }
 
     Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][z]=2").
-      must_equal "x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}
+      must_equal "x" => { "y" => [{ "z" => "1" }, { "z" => "2" }] }
     Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3").
-      must_equal "x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}
+      must_equal "x" => { "y" => [{ "z" => "1", "w" => "a" }, { "z" => "2", "w" => "3" }] }
 
     Rack::Utils.parse_nested_query("x[][y]=1&x[][z][w]=a&x[][y]=2&x[][z][w]=b").
-      must_equal "x" => [{"y" => "1", "z" => {"w" => "a"}}, {"y" => "2", "z" => {"w" => "b"}}]
+      must_equal "x" => [{ "y" => "1", "z" => { "w" => "a" } }, { "y" => "2", "z" => { "w" => "b" } }]
     Rack::Utils.parse_nested_query("x[][z][w]=a&x[][y]=1&x[][z][w]=b&x[][y]=2").
-      must_equal "x" => [{"y" => "1", "z" => {"w" => "a"}}, {"y" => "2", "z" => {"w" => "b"}}]
+      must_equal "x" => [{ "y" => "1", "z" => { "w" => "a" } }, { "y" => "2", "z" => { "w" => "b" } }]
 
     Rack::Utils.parse_nested_query("data[books][][data][page]=1&data[books][][data][page]=2").
-      must_equal "data" => { "books" => [{ "data" => { "page" => "1"}}, { "data" => { "page" => "2"}}] }
+      must_equal "data" => { "books" => [{ "data" => { "page" => "1" } }, { "data" => { "page" => "2" } }] }
 
     lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y]z=2") }.
       must_raise(Rack::Utils::ParameterTypeError).
@@ -231,13 +232,25 @@ describe Rack::Utils do
       message.must_equal "invalid byte sequence in UTF-8"
   end
 
+  it "only moves to a new array when the full key has been seen" do
+    Rack::Utils.parse_nested_query("x[][y][][z]=1&x[][y][][w]=2").
+      must_equal "x" => [{ "y" => [{ "z" => "1", "w" => "2" }] }]
+
+    Rack::Utils.parse_nested_query(
+      "x[][id]=1&x[][y][a]=5&x[][y][b]=7&x[][z][id]=3&x[][z][w]=0&x[][id]=2&x[][y][a]=6&x[][y][b]=8&x[][z][id]=4&x[][z][w]=0"
+    ).must_equal "x" => [
+        { "id" => "1", "y" => { "a" => "5", "b" => "7" }, "z" => { "id" => "3", "w" => "0" } },
+        { "id" => "2", "y" => { "a" => "6", "b" => "8" }, "z" => { "id" => "4", "w" => "0" } },
+      ]
+  end
+
   it "allow setting the params hash class to use for parsing query strings" do
     begin
       default_parser = Rack::Utils.default_query_parser
       param_parser_class = Class.new(Rack::QueryParser::Params) do
         def initialize(*)
           super
-          @params = Hash.new{|h,k| h[k.to_s] if k.is_a?(Symbol)}
+          @params = Hash.new{|h, k| h[k.to_s] if k.is_a?(Symbol)}
         end
       end
       Rack::Utils.default_query_parser = Rack::QueryParser.new(param_parser_class, 65536, 100)
@@ -308,7 +321,7 @@ describe Rack::Utils do
       must_equal 'x[y][][z]=1&x[y][][z]=2'
     Rack::Utils.build_nested_query('x' => { 'y' => [{ 'z' => '1', 'w' => 'a' }, { 'z' => '2', 'w' => '3' }] }).
       must_equal 'x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3'
-    Rack::Utils.build_nested_query({"foo" => ["1", ["2"]]}).
+    Rack::Utils.build_nested_query({ "foo" => ["1", ["2"]] }).
       must_equal 'foo[]=1&foo[][]=2'
 
     lambda { Rack::Utils.build_nested_query("foo=bar") }.
@@ -317,24 +330,24 @@ describe Rack::Utils do
   end
 
   it 'performs the inverse function of #parse_nested_query' do
-    [{"foo" => nil, "bar" => ""},
-      {"foo" => "bar", "baz" => ""},
-      {"foo" => ["1", "2"]},
-      {"foo" => "bar", "baz" => ["1", "2", "3"]},
-      {"foo" => ["bar"], "baz" => ["1", "2", "3"]},
-      {"foo" => ["1", "2"]},
-      {"foo" => "bar", "baz" => ["1", "2", "3"]},
-      {"x" => {"y" => {"z" => "1"}}},
-      {"x" => {"y" => {"z" => ["1"]}}},
-      {"x" => {"y" => {"z" => ["1", "2"]}}},
-      {"x" => {"y" => [{"z" => "1"}]}},
-      {"x" => {"y" => [{"z" => ["1"]}]}},
-      {"x" => {"y" => [{"z" => "1", "w" => "2"}]}},
-      {"x" => {"y" => [{"v" => {"w" => "1"}}]}},
-      {"x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}},
-      {"x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}},
-      {"x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}},
-      {"foo" => ["1", ["2"]]},
+    [{ "foo" => nil, "bar" => "" },
+      { "foo" => "bar", "baz" => "" },
+      { "foo" => ["1", "2"] },
+      { "foo" => "bar", "baz" => ["1", "2", "3"] },
+      { "foo" => ["bar"], "baz" => ["1", "2", "3"] },
+      { "foo" => ["1", "2"] },
+      { "foo" => "bar", "baz" => ["1", "2", "3"] },
+      { "x" => { "y" => { "z" => "1" } } },
+      { "x" => { "y" => { "z" => ["1"] } } },
+      { "x" => { "y" => { "z" => ["1", "2"] } } },
+      { "x" => { "y" => [{ "z" => "1" }] } },
+      { "x" => { "y" => [{ "z" => ["1"] }] } },
+      { "x" => { "y" => [{ "z" => "1", "w" => "2" }] } },
+      { "x" => { "y" => [{ "v" => { "w" => "1" } }] } },
+      { "x" => { "y" => [{ "z" => "1", "v" => { "w" => "2" } }] } },
+      { "x" => { "y" => [{ "z" => "1" }, { "z" => "2" }] } },
+      { "x" => { "y" => [{ "z" => "1", "w" => "a" }, { "z" => "2", "w" => "3" }] } },
+      { "foo" => ["1", ["2"]] },
     ].each { |params|
       qs = Rack::Utils.build_nested_query(params)
       Rack::Utils.parse_nested_query(qs).must_equal params
@@ -382,7 +395,7 @@ describe Rack::Utils do
     Rack::Utils.best_q_match("text/plain,text/html", %w[text/html text/plain]).must_equal "text/html"
 
     # When there are no matches, return nil:
-    Rack::Utils.best_q_match("application/json", %w[text/html text/plain]).must_equal nil
+    Rack::Utils.best_q_match("application/json", %w[text/html text/plain]).must_be_nil
   end
 
   it "escape html entities [&><'\"/]" do
@@ -415,9 +428,9 @@ describe Rack::Utils do
       Rack::Utils.select_best_encoding(a, b)
     end
 
-    helper.call(%w(), [["x", 1]]).must_equal nil
-    helper.call(%w(identity), [["identity", 0.0]]).must_equal nil
-    helper.call(%w(identity), [["*", 0.0]]).must_equal nil
+    helper.call(%w(), [["x", 1]]).must_be_nil
+    helper.call(%w(identity), [["identity", 0.0]]).must_be_nil
+    helper.call(%w(identity), [["*", 0.0]]).must_be_nil
 
     helper.call(%w(identity), [["compress", 1.0], ["gzip", 1.0]]).must_equal "identity"
 
@@ -449,6 +462,12 @@ describe Rack::Utils do
     Rack::Utils.status_code(:ok).must_equal 200
   end
 
+  it "raise an error for an invalid symbol" do
+    assert_raises(ArgumentError, "Unrecognized status code :foobar") do
+      Rack::Utils.status_code(:foobar)
+    end
+  end
+
   it "return rfc2822 format from rfc2822 helper" do
     Rack::Utils.rfc2822(Time.at(0).gmtime).must_equal "Thu, 01 Jan 1970 00:00:00 -0000"
   end
@@ -480,19 +499,19 @@ end
 describe Rack::Utils, "cookies" do
   it "parses cookies" do
     env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "zoo=m")
-    Rack::Utils.parse_cookies(env).must_equal({"zoo" => "m"})
+    Rack::Utils.parse_cookies(env).must_equal({ "zoo" => "m" })
 
     env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=%")
-    Rack::Utils.parse_cookies(env).must_equal({"foo" => "%"})
+    Rack::Utils.parse_cookies(env).must_equal({ "foo" => "%" })
 
     env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;foo=car")
-    Rack::Utils.parse_cookies(env).must_equal({"foo" => "bar"})
+    Rack::Utils.parse_cookies(env).must_equal({ "foo" => "bar" })
 
     env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m")
-    Rack::Utils.parse_cookies(env).must_equal({"foo" => "bar", "quux" => "h&m"})
+    Rack::Utils.parse_cookies(env).must_equal({ "foo" => "bar", "quux" => "h&m" })
 
     env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar").freeze
-    Rack::Utils.parse_cookies(env).must_equal({"foo" => "bar"})
+    Rack::Utils.parse_cookies(env).must_equal({ "foo" => "bar" })
   end
 
   it "adds new cookies to nil header" do
@@ -526,48 +545,48 @@ end
 
 describe Rack::Utils, "byte_range" do
   it "ignore missing or syntactically invalid byte ranges" do
-    Rack::Utils.byte_ranges({},500).must_equal nil
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "foobar"},500).must_equal nil
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "furlongs=123-456"},500).must_equal nil
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes="},500).must_equal nil
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-"},500).must_equal nil
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123,456"},500).must_equal nil
+    Rack::Utils.byte_ranges({}, 500).must_be_nil
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "foobar" }, 500).must_be_nil
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "furlongs=123-456" }, 500).must_be_nil
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=" }, 500).must_be_nil
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=-" }, 500).must_be_nil
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=123,456" }, 500).must_be_nil
     # A range of non-positive length is syntactically invalid and ignored:
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=456-123"},500).must_equal nil
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=456-455"},500).must_equal nil
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=456-123" }, 500).must_be_nil
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=456-455" }, 500).must_be_nil
   end
 
   it "parse simple byte ranges" do
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-456"},500).must_equal [(123..456)]
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-"},500).must_equal [(123..499)]
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-100"},500).must_equal [(400..499)]
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-0"},500).must_equal [(0..0)]
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=499-499"},500).must_equal [(499..499)]
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=123-456" }, 500).must_equal [(123..456)]
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=123-" }, 500).must_equal [(123..499)]
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=-100" }, 500).must_equal [(400..499)]
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=0-0" }, 500).must_equal [(0..0)]
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=499-499" }, 500).must_equal [(499..499)]
   end
 
   it "parse several byte ranges" do
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-600,601-999"},1000).must_equal [(500..600),(601..999)]
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=500-600,601-999" }, 1000).must_equal [(500..600), (601..999)]
   end
 
   it "truncate byte ranges" do
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-999"},500).must_equal [(123..499)]
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=600-999"},500).must_equal []
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-999"},500).must_equal [(0..499)]
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=123-999" }, 500).must_equal [(123..499)]
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=600-999" }, 500).must_equal []
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=-999" }, 500).must_equal [(0..499)]
   end
 
   it "ignore unsatisfiable byte ranges" do
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-501"},500).must_equal []
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-"},500).must_equal []
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=999-"},500).must_equal []
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-0"},500).must_equal []
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=500-501" }, 500).must_equal []
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=500-" }, 500).must_equal []
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=999-" }, 500).must_equal []
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=-0" }, 500).must_equal []
   end
 
   it "handle byte ranges of empty files" do
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-456"},0).must_equal []
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-"},0).must_equal []
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-100"},0).must_equal []
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-0"},0).must_equal []
-    Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-0"},0).must_equal []
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=123-456" }, 0).must_equal []
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=0-" }, 0).must_equal []
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=-100" }, 0).must_equal []
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=0-0" }, 0).must_equal []
+    Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=-0" }, 0).must_equal []
   end
 end
 
@@ -600,7 +619,7 @@ describe Rack::Utils::HeaderHash do
   it "merge case-insensitively" do
     h = Rack::Utils::HeaderHash.new("ETag" => 'HELLO', "content-length" => '123')
     merged = h.merge("Etag" => 'WORLD', 'Content-Length' => '321', "Foo" => 'BAR')
-    merged.must_equal "Etag"=>'WORLD', "Content-Length"=>'321', "Foo"=>'BAR'
+    merged.must_equal "Etag" => 'WORLD', "Content-Length" => '321', "Foo" => 'BAR'
   end
 
   it "overwrite case insensitively and assume the new key's case" do
@@ -623,7 +642,7 @@ describe Rack::Utils::HeaderHash do
 
   it "replace hashes correctly" do
     h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
-    j = {"foo" => "bar"}
+    j = { "foo" => "bar" }
     h.replace(j)
     h["foo"].must_equal "bar"
   end
@@ -652,16 +671,16 @@ describe Rack::Utils::HeaderHash do
     h.delete("Hello").must_be_nil
   end
 
-  it "avoid unnecessary object creation if possible" do
+  it "dups given HeaderHash" do
     a = Rack::Utils::HeaderHash.new("foo" => "bar")
     b = Rack::Utils::HeaderHash.new(a)
-    b.object_id.must_equal a.object_id
+    b.object_id.wont_equal a.object_id
     b.must_equal a
   end
 
   it "convert Array values to Strings when responding to #each" do
     h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
-    h.each do |k,v|
+    h.each do |k, v|
       k.must_equal "foo"
       v.must_equal "bar\nbaz"
     end
@@ -678,14 +697,14 @@ end
 describe Rack::Utils::Context do
   class ContextTest
     attr_reader :app
-    def initialize app; @app=app; end
+    def initialize app; @app = app; end
     def call env; context env; end
-    def context env, app=@app; app.call(env); end
+    def context env, app = @app; app.call(env); end
   end
-  test_target1 = proc{|e| e.to_s+' world' }
-  test_target2 = proc{|e| e.to_i+2 }
+  test_target1 = proc{|e| e.to_s + ' world' }
+  test_target2 = proc{|e| e.to_i + 2 }
   test_target3 = proc{|e| nil }
-  test_target4 = proc{|e| [200,{'Content-Type'=>'text/plain', 'Content-Length'=>'0'},['']] }
+  test_target4 = proc{|e| [200, { 'Content-Type' => 'text/plain', 'Content-Length' => '0' }, ['']] }
   test_app = ContextTest.new test_target4
 
   it "set context correctly" do
diff --git a/test/spec_version.rb b/test/spec_version.rb
index 6ab0a74c..d4191aa4 100644
--- a/test/spec_version.rb
+++ b/test/spec_version.rb
@@ -1,5 +1,6 @@
-# -*- encoding: utf-8 -*-
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack'
 
 describe Rack do
diff --git a/test/spec_webrick.rb b/test/spec_webrick.rb
index 9ae6103d..0d0aa8f7 100644
--- a/test/spec_webrick.rb
+++ b/test/spec_webrick.rb
@@ -1,6 +1,8 @@
-require 'minitest/autorun'
+# frozen_string_literal: true
+
+require 'minitest/global_expectations/autorun'
 require 'rack/mock'
-require 'concurrent/atomic/count_down_latch'
+require 'thread'
 require File.expand_path('../testrequest', __FILE__)
 
 Thread.abort_on_exception = true
@@ -9,10 +11,10 @@ describe Rack::Handler::WEBrick do
   include TestRequest::Helpers
 
   before do
-  @server = WEBrick::HTTPServer.new(:Host => @host='127.0.0.1',
-                                    :Port => @port=9202,
-                                    :Logger => WEBrick::Log.new(nil, WEBrick::BasicLog::WARN),
-                                    :AccessLog => [])
+  @server = WEBrick::HTTPServer.new(Host: @host = '127.0.0.1',
+                                    Port: @port = 9202,
+                                    Logger: WEBrick::Log.new(nil, WEBrick::BasicLog::WARN),
+                                    AccessLog: [])
   @server.mount "/test", Rack::Handler::WEBrick,
     Rack::Lint.new(TestRequest.new)
   @thread = Thread.new { @server.start }
@@ -49,7 +51,7 @@ describe Rack::Handler::WEBrick do
 
   it "have rack headers" do
     GET("/test")
-    response["rack.version"].must_equal [1,3]
+    response["rack.version"].must_equal [1, 3]
     response["rack.multithread"].must_equal true
     assert_equal false, response["rack.multiprocess"]
     assert_equal false, response["rack.run_once"]
@@ -80,7 +82,7 @@ describe Rack::Handler::WEBrick do
   end
 
   it "have CGI headers on POST" do
-    POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
+    POST("/test", { "rack-form-data" => "23" }, { 'X-test-header' => '42' })
     status.must_equal 200
     response["REQUEST_METHOD"].must_equal "POST"
     response["SCRIPT_NAME"].must_equal "/test"
@@ -92,7 +94,7 @@ describe Rack::Handler::WEBrick do
   end
 
   it "support HTTP auth" do
-    GET("/test", {:user => "ruth", :passwd => "secret"})
+    GET("/test", { user: "ruth", passwd: "secret" })
     response["HTTP_AUTHORIZATION"].must_equal "Basic cnV0aDpzZWNyZXQ="
   end
 
@@ -119,25 +121,33 @@ describe Rack::Handler::WEBrick do
   end
 
   it "provide a .run" do
-    block_ran = false
-    latch = Concurrent::CountDownLatch.new 1
+    queue = Queue.new
 
     t = Thread.new do
       Rack::Handler::WEBrick.run(lambda {},
                                  {
-                                   :Host => '127.0.0.1',
-                                   :Port => 9210,
-                                   :Logger => WEBrick::Log.new(nil, WEBrick::BasicLog::WARN),
-                                   :AccessLog => []}) { |server|
-        block_ran = true
+                                   Host: '127.0.0.1',
+                                   Port: 9210,
+                                   Logger: WEBrick::Log.new(nil, WEBrick::BasicLog::WARN),
+                                   AccessLog: [] }) { |server|
         assert_kind_of WEBrick::HTTPServer, server
-        @s = server
-        latch.count_down
+        queue.push(server)
       }
     end
 
-    latch.wait
-    @s.shutdown
+    server = queue.pop
+
+    # The server may not yet have started: wait for it
+    seconds = 10
+    wait_time = 0.1
+    until server.status == :Running || seconds <= 0
+      seconds -= wait_time
+      sleep wait_time
+    end
+
+    raise "Server never reached status 'Running'" unless server.status == :Running
+
+    server.shutdown
     t.join
   end
 
@@ -171,7 +181,7 @@ describe Rack::Handler::WEBrick do
     Rack::Lint.new(lambda{ |req|
       [
         200,
-        {"rack.hijack" => io_lambda},
+        [ [ "rack.hijack", io_lambda ] ],
         [""]
       ]
     })
@@ -187,7 +197,7 @@ describe Rack::Handler::WEBrick do
     Rack::Lint.new(lambda{ |req|
       [
         200,
-        {"Transfer-Encoding" => "chunked"},
+        { "Transfer-Encoding" => "chunked" },
         ["7\r\nchunked\r\n0\r\n\r\n"]
       ]
     })
@@ -195,14 +205,14 @@ describe Rack::Handler::WEBrick do
     Net::HTTP.start(@host, @port){ |http|
       res = http.get("/chunked")
       res["Transfer-Encoding"].must_equal "chunked"
-      res["Content-Length"].must_equal nil
+      res["Content-Length"].must_be_nil
       res.body.must_equal "chunked"
     }
   end
 
   after do
-  @status_thread.join
-  @server.shutdown
-  @thread.join
+    @status_thread.join
+    @server.shutdown
+    @thread.join
   end
 end
diff --git a/test/testrequest.rb b/test/testrequest.rb
index cacd23d5..aabe7fa6 100644
--- a/test/testrequest.rb
+++ b/test/testrequest.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'yaml'
 require 'net/http'
 require 'rack/lint'
@@ -10,10 +12,10 @@ class TestRequest
     env["test.postdata"] = env["rack.input"].read
     minienv = env.dup
     # This may in the future want to replace with a dummy value instead.
-    minienv.delete_if { |k,v| NOSERIALIZE.any? { |c| v.kind_of?(c) } }
+    minienv.delete_if { |k, v| NOSERIALIZE.any? { |c| v.kind_of?(c) } }
     body = minienv.to_yaml
     size = body.bytesize
-    [status, {"Content-Type" => "text/yaml", "Content-Length" => size.to_s}, [body]]
+    [status, { "Content-Type" => "text/yaml", "Content-Length" => size.to_s }, [body]]
   end
 
   module Helpers
@@ -30,7 +32,7 @@ class TestRequest
       "#{ROOT}/bin/rackup"
     end
 
-    def GET(path, header={})
+    def GET(path, header = {})
       Net::HTTP.start(@host, @port) { |http|
         user = header.delete(:user)
         passwd = header.delete(:passwd)
@@ -48,7 +50,7 @@ class TestRequest
       }
     end
 
-    def POST(path, formdata={}, header={})
+    def POST(path, formdata = {}, header = {})
       Net::HTTP.start(@host, @port) { |http|
         user = header.delete(:user)
         passwd = header.delete(:passwd)
@@ -67,7 +69,7 @@ end
 
 class StreamingRequest
   def self.call(env)
-    [200, {"Content-Type" => "text/plain"}, new]
+    [200, { "Content-Type" => "text/plain" }, new]
   end
 
   def each
diff --git a/test/unregistered_handler/rack/handler/unregistered.rb b/test/unregistered_handler/rack/handler/unregistered.rb
index 3ca5a72c..e98468cc 100644
--- a/test/unregistered_handler/rack/handler/unregistered.rb
+++ b/test/unregistered_handler/rack/handler/unregistered.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Rack
   module Handler
     # this class doesn't do anything, we're just seeing if we get it.
diff --git a/test/unregistered_handler/rack/handler/unregistered_long_one.rb b/test/unregistered_handler/rack/handler/unregistered_long_one.rb
index 2c2fae17..87c6c254 100644
--- a/test/unregistered_handler/rack/handler/unregistered_long_one.rb
+++ b/test/unregistered_handler/rack/handler/unregistered_long_one.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Rack
   module Handler
     # this class doesn't do anything, we're just seeing if we get it.