Skip to content

Commit

Permalink
Merge branch 'main' into cloudfront-forwarded-proto-header
Browse files Browse the repository at this point in the history
  • Loading branch information
ioquatix committed Mar 20, 2024
2 parents 13d80d2 + 487178f commit e4a933f
Show file tree
Hide file tree
Showing 28 changed files with 366 additions and 182 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/depsreview.yaml
Expand Up @@ -9,6 +9,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: 'Dependency Review'
uses: actions/dependency-review-action@v3
uses: actions/dependency-review-action@v4
4 changes: 2 additions & 2 deletions .github/workflows/test-external.yaml
Expand Up @@ -11,14 +11,14 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest]
ruby: ['2.7', '3.0', '3.1']
ruby: ['3.0', '3.1', '3.2', '3.3']

runs-on: ${{matrix.os}}
env:
CI: external

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: ruby/setup-ruby-pkgs@v1
with:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/test.yaml
Expand Up @@ -20,6 +20,7 @@ jobs:
- '3.0'
- '3.1'
- '3.2'
- '3.3'
- jruby
- truffleruby-head
include:
Expand All @@ -30,7 +31,7 @@ jobs:
CI: spec

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: ruby/setup-ruby@v1
with:
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Expand Up @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. For info on
### SPEC Changes

- `rack.input` is now optional. ([#1997](https://github.com/rack/rack/pull/1997), [@ioquatix])
- `Rack::Utils.escape_html` is now delegated to `CGI.escapeHTML`. `'` is escaped to `#39;` instead of `#x27;`. (decimal vs hexadecimal) ([#2099](https://github.com/rack/rack/pull/2099), [@JunichiIto](https://github.com/JunichiIto))

### Changed

Expand All @@ -15,8 +16,24 @@ All notable changes to this project will be documented in this file. For info on
- MIME type for JavaScript files (`.js`) changed from `application/javascript` to `text/javascript` ([`1bd0f15`](https://github.com/rack/rack/commit/1bd0f1597d8f4a90d47115f3e156a8ce7870c9c8))
- Add `.mjs` MIME type ([#2057](https://github.com/rack/rack/pull/2057), [@axilleas])
- Update MIME types associated to `.ttf`, `.woff`, `.woff2` and `.otf` extensions to use mondern `font/*` types. ([#2065](https://github.com/rack/rack/pull/2065), [@davidstosik])
- `set_cookie_header` utility now supports the `partitioned` cookie attribute. This is required by Chrome in some embedded contexts. ([#2131](https://github.com/rack/rack/pull/2131), [@flavio-b])
- Remove non-standard status codes 306, 509, & 510 and update descriptions for 413, 422, & 451. ([#2137](https://github.com/rack/rack/pull/2137), [@wtn])
- Add fallback lookup and deprecation warning for obsolete status symbols. ([#2137](https://github.com/rack/rack/pull/2137), [@wtn])
- In `Rack::Files`, ignore the `Range` header if served file is 0 bytes. ([#2159](https://github.com/rack/rack/pull/2159), [@zarqman])
- Add `Rack::SetXForwardedProtoHeader` middleware ([#2089](https://github.com/rack/rack/pull/2089), [@tomharvey])

## [3.0.10] - 2024-03-21

- Backport #2104 to 3-0-stable: Return empty when parsing a multi-part POST with only one end delimiter. ([#2164](https://github.com/rack/rack/pull/2164), [@JoeDupuis](https://github.com/JoeDupuis))

## [3.0.9] - 2024-01-31

- Fix incorrect content-length header that was emitted when `Rack::Response#write` was used in some situations. ([#2150](https://github.com/rack/rack/pull/2150), [@mattbrictson](https://github.com/mattbrictson))

## [3.0.8] - 2023-06-14

- Fix some unused variable verbose warnings. ([#2084](https://github.com/rack/rack/pull/2084), [@jeremyevans], [@skipkayhil](https://github.com/skipkayhil))

## [3.0.7] - 2023-03-16

- Make query parameters without `=` have `nil` values. ([#2059](https://github.com/rack/rack/pull/2059), [@jeremyevans])
Expand Down
5 changes: 2 additions & 3 deletions SPEC.rdoc
Expand Up @@ -112,7 +112,6 @@ The <tt>SERVER_PORT</tt> must be an Integer if set.
The <tt>SERVER_NAME</tt> must be a valid authority as defined by RFC7540.
The <tt>HTTP_HOST</tt> must be a valid authority as defined by RFC7540.
The <tt>SERVER_PROTOCOL</tt> must match the regexp <tt>HTTP/\d(\.\d)?</tt>.
If the <tt>HTTP_VERSION</tt> is present, it must equal the <tt>SERVER_PROTOCOL</tt>.
The environment must not contain the keys
<tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
(use the versions without <tt>HTTP_</tt>).
Expand All @@ -126,7 +125,7 @@ There are the following restrictions:
* There may be a valid hijack callback in <tt>rack.hijack</tt>
* The <tt>REQUEST_METHOD</tt> must be a valid token.
* The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
* The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
* The <tt>PATH_INFO</tt>, if non-empty (or the request is something other than <tt>OPTIONS *</tt>), must start with <tt>/</tt>
* The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
* One of <tt>SCRIPT_NAME</tt> or <tt>PATH_INFO</tt> must be
set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
Expand Down Expand Up @@ -301,7 +300,7 @@ the body.

The Enumerable Body must respond to +each+.
It must only be called once.
It must not be called after being closed.
It must not be called after being closed,
and must only yield String values.

The Body itself should not be an instance of String, as this will
Expand Down
6 changes: 6 additions & 0 deletions UPGRADE-GUIDE.md
Expand Up @@ -180,6 +180,12 @@ this was only generally possible with a file based backing, which prevented
efficient streaming of request bodies. Now, `rack.input` is not required to be
rewindable.

### `rack.input` is no longer rewound after consuming form and multipart data

Previously `.rewind` was called after consuming form and multipart data. Use
`Rack::RewindableInput::Middleware` to make the body rewindable, and call
`.rewind` explicitly to match this behavior.

## Response Changes

### Response must be mutable
Expand Down
24 changes: 9 additions & 15 deletions lib/rack.rb
Expand Up @@ -16,22 +16,20 @@

module Rack
autoload :BadRequest, "rack/bad_request"
autoload :Builder, "rack/builder"
autoload :BodyProxy, "rack/body_proxy"
autoload :Builder, "rack/builder"
autoload :Cascade, "rack/cascade"
autoload :CommonLogger, "rack/common_logger"
autoload :ConditionalGet, "rack/conditional_get"
autoload :Config, "rack/config"
autoload :ContentLength, "rack/content_length"
autoload :ContentType, "rack/content_type"
autoload :Deflater, "rack/deflater"
autoload :Directory, "rack/directory"
autoload :ETag, "rack/etag"
autoload :Events, "rack/events"
autoload :File, "rack/file"
autoload :Files, "rack/files"
autoload :Deflater, "rack/deflater"
autoload :Directory, "rack/directory"
autoload :ForwardRequest, "rack/recursive"
autoload :Handler, "rack/handler"
autoload :Head, "rack/head"
autoload :Headers, "rack/headers"
autoload :Lint, "rack/lint"
Expand All @@ -40,33 +38,29 @@ module Rack
autoload :MediaType, "rack/media_type"
autoload :MethodOverride, "rack/method_override"
autoload :Mime, "rack/mime"
autoload :MockRequest, "rack/mock_request"
autoload :MockResponse, "rack/mock_response"
autoload :Multipart, "rack/multipart"
autoload :NullLogger, "rack/null_logger"
autoload :QueryParser, "rack/query_parser"
autoload :Recursive, "rack/recursive"
autoload :Reloader, "rack/reloader"
autoload :Request, "rack/request"
autoload :Response, "rack/response"
autoload :RewindableInput, "rack/rewindable_input"
autoload :Runtime, "rack/runtime"
autoload :Sendfile, "rack/sendfile"
autoload :Server, "rack/server"
autoload :SetXForwardedProtoHeader, "rack/set_x_forwarded_proto_header"
autoload :ShowExceptions, "rack/show_exceptions"
autoload :ShowStatus, "rack/show_status"
autoload :Static, "rack/static"
autoload :TempfileReaper, "rack/tempfile_reaper"
autoload :URLMap, "rack/urlmap"
autoload :Utils, "rack/utils"
autoload :Multipart, "rack/multipart"

autoload :MockRequest, "rack/mock_request"
autoload :MockResponse, "rack/mock_response"

autoload :Request, "rack/request"
autoload :Response, "rack/response"

module Auth
autoload :Basic, "rack/auth/basic"
autoload :AbstractRequest, "rack/auth/abstract/request"
autoload :AbstractHandler, "rack/auth/abstract/handler"
autoload :Digest, "rack/auth/digest"
autoload :AbstractRequest, "rack/auth/abstract/request"
end
end
3 changes: 1 addition & 2 deletions lib/rack/auth/basic.rb
Expand Up @@ -2,7 +2,6 @@

require_relative 'abstract/handler'
require_relative 'abstract/request'
require 'base64'

module Rack
module Auth
Expand Down Expand Up @@ -46,7 +45,7 @@ def basic?
end

def credentials
@credentials ||= Base64.decode64(params).split(':', 2)
@credentials ||= params.unpack1('m').split(':', 2)
end

def username
Expand Down
29 changes: 21 additions & 8 deletions lib/rack/builder.rb
Expand Up @@ -2,6 +2,9 @@

require_relative 'urlmap'

module Rack; end
Rack::BUILDER_TOPLEVEL_BINDING = ->(builder){builder.instance_eval{binding}}

module Rack
# Rack::Builder provides a domain-specific language (DSL) to construct Rack
# applications. It is primarily used to parse +config.ru+ files which
Expand Down Expand Up @@ -59,9 +62,9 @@ class Builder
# # requires ./my_app.rb, which should be in the
# # process's current directory. After requiring,
# # assumes MyApp constant is a Rack application
def self.parse_file(path)
def self.parse_file(path, **options)
if path.end_with?('.ru')
return self.load_file(path)
return self.load_file(path, **options)
else
require path
return Object.const_get(::File.basename(path, '.rb').split('_').map(&:capitalize).join(''))
Expand All @@ -81,7 +84,7 @@ def self.parse_file(path)
# use Rack::ContentLength
# require './app.rb'
# run App
def self.load_file(path)
def self.load_file(path, **options)
config = ::File.read(path)
config.slice!(/\A#{UTF_8_BOM}/) if config.encoding == Encoding::UTF_8

Expand All @@ -91,33 +94,43 @@ def self.load_file(path)

config.sub!(/^__END__\n.*\Z/m, '')

return new_from_string(config, path)
return new_from_string(config, path, **options)
end

# Evaluate the given +builder_script+ string in the context of
# a Rack::Builder block, returning a Rack application.
def self.new_from_string(builder_script, file = "(rackup)")
def self.new_from_string(builder_script, path = "(rackup)", **options)
builder = self.new(**options)

# We want to build a variant of TOPLEVEL_BINDING with self as a Rack::Builder instance.
# We cannot use instance_eval(String) as that would resolve constants differently.
binding, builder = TOPLEVEL_BINDING.eval('Rack::Builder.new.instance_eval { [binding, self] }')
eval builder_script, binding, file
binding = BUILDER_TOPLEVEL_BINDING.call(builder)
eval(builder_script, binding, path)

return builder.to_app
end

# Initialize a new Rack::Builder instance. +default_app+ specifies the
# default application if +run+ is not called later. If a block
# is given, it is evaluated in the context of the instance.
def initialize(default_app = nil, &block)
def initialize(default_app = nil, **options, &block)
@use = []
@map = nil
@run = default_app
@warmup = nil
@freeze_app = false
@options = options

instance_eval(&block) if block_given?
end

# Any options provided to the Rack::Builder instance at initialization.
# These options can be server-specific. Some general options are:
#
# * +:isolation+: One of +process+, +thread+ or +fiber+. The execution
# isolation model to use.
attr :options

# Create a new Rack::Builder instance and return the Rack application
# generated from it.
def self.app(default_app = nil, &block)
Expand Down
1 change: 1 addition & 0 deletions lib/rack/constants.rb
Expand Up @@ -54,6 +54,7 @@ module Rack
RACK_RESPONSE_FINISHED = 'rack.response_finished'
RACK_REQUEST_FORM_INPUT = 'rack.request.form_input'
RACK_REQUEST_FORM_HASH = 'rack.request.form_hash'
RACK_REQUEST_FORM_PAIRS = 'rack.request.form_pairs'
RACK_REQUEST_FORM_VARS = 'rack.request.form_vars'
RACK_REQUEST_FORM_ERROR = 'rack.request.form_error'
RACK_REQUEST_COOKIE_HASH = 'rack.request.cookie_hash'
Expand Down
1 change: 0 additions & 1 deletion lib/rack/headers.rb
Expand Up @@ -74,7 +74,6 @@ class Headers < Hash
X-Correlation-Id
X-Download-Options
X-Frame-Options
X-Frame-Options
X-Permitted-Cross-Domain-Policies
X-Powered-By
X-Redirect-By
Expand Down
12 changes: 4 additions & 8 deletions lib/rack/lint.rb
Expand Up @@ -288,11 +288,6 @@ def check_environment(env)
raise LintError, "env[SERVER_PROTOCOL] does not match HTTP/\\d(\\.\\d)?"
end

## If the <tt>HTTP_VERSION</tt> is present, it must equal the <tt>SERVER_PROTOCOL</tt>.
if env['HTTP_VERSION'] && env['HTTP_VERSION'] != server_protocol
raise LintError, "env[HTTP_VERSION] does not equal env[SERVER_PROTOCOL]"
end

## The environment must not contain the keys
## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
## (use the versions without <tt>HTTP_</tt>).
Expand Down Expand Up @@ -346,8 +341,9 @@ def check_environment(env)
if env.include?(SCRIPT_NAME) && env[SCRIPT_NAME] != "" && env[SCRIPT_NAME] !~ /\A\//
raise LintError, "SCRIPT_NAME must start with /"
end
## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
if env.include?(PATH_INFO) && env[PATH_INFO] != "" && env[PATH_INFO] !~ /\A\//

## * The <tt>PATH_INFO</tt>, if non-empty (or the request is something other than <tt>OPTIONS *</tt>), must start with <tt>/</tt>
if env.include?(PATH_INFO) && !(env[REQUEST_METHOD] == OPTIONS && env[PATH_INFO] == ?*) && env[PATH_INFO] != "" && env[PATH_INFO] !~ /\A\//
raise LintError, "PATH_INFO must start with /"
end
## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
Expand Down Expand Up @@ -784,7 +780,7 @@ def each
## It must only be called once.
raise LintError, "Response body must only be invoked once (#{@invoked})" unless @invoked.nil?

## It must not be called after being closed.
## It must not be called after being closed,
raise LintError, "Response body is already closed" if @closed

@invoked = :each
Expand Down
13 changes: 9 additions & 4 deletions lib/rack/media_type.rb
Expand Up @@ -4,7 +4,7 @@ module Rack
# Rack::MediaType parse media type and parameters out of content_type string

class MediaType
SPLIT_PATTERN = %r{\s*[;,]\s*}
SPLIT_PATTERN = /[;,]/

class << self
# The media type (type/subtype) portion of the CONTENT_TYPE header
Expand All @@ -15,7 +15,11 @@ class << self
# 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.tap(&:downcase!)
if type = content_type.split(SPLIT_PATTERN, 2).first
type.rstrip!
type.downcase!
type
end
end

# The media type parameters provided in CONTENT_TYPE as a Hash, or
Expand All @@ -27,9 +31,10 @@ def params(content_type)
return {} if content_type.nil?

content_type.split(SPLIT_PATTERN)[1..-1].each_with_object({}) do |s, hsh|
s.strip!
k, v = s.split('=', 2)

hsh[k.tap(&:downcase!)] = strip_doublequotes(v)
k.downcase!
hsh[k] = strip_doublequotes(v)
end
end

Expand Down
17 changes: 13 additions & 4 deletions lib/rack/mock_request.rb
Expand Up @@ -139,14 +139,23 @@ def self.env_for(uri = "", opts = {})
end
end

if String === opts[:input]
rack_input = StringIO.new(opts[:input])
input = opts[:input]
if String === input
rack_input = StringIO.new(input)
rack_input.set_encoding(Encoding::BINARY)
else
rack_input = opts[:input]
if input.respond_to?(:encoding) && input.encoding != Encoding::BINARY
warn "input encoding not binary", uplevel: 1
if input.respond_to?(:set_encoding)
input.set_encoding(Encoding::BINARY)
else
raise ArgumentError, "could not coerce input to binary encoding"
end
end
rack_input = input
end

if rack_input
rack_input.set_encoding(Encoding::BINARY)
env[RACK_INPUT] = rack_input

env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size)
Expand Down

0 comments on commit e4a933f

Please sign in to comment.