rack 3.0.15 → 3.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +368 -6
  3. data/CONTRIBUTING.md +11 -9
  4. data/README.md +103 -28
  5. data/SPEC.rdoc +206 -288
  6. data/lib/rack/auth/abstract/request.rb +2 -0
  7. data/lib/rack/auth/basic.rb +1 -2
  8. data/lib/rack/bad_request.rb +8 -0
  9. data/lib/rack/builder.rb +29 -10
  10. data/lib/rack/cascade.rb +0 -3
  11. data/lib/rack/conditional_get.rb +4 -3
  12. data/lib/rack/constants.rb +4 -0
  13. data/lib/rack/directory.rb +6 -3
  14. data/lib/rack/events.rb +21 -6
  15. data/lib/rack/files.rb +1 -1
  16. data/lib/rack/head.rb +2 -3
  17. data/lib/rack/headers.rb +86 -2
  18. data/lib/rack/lint.rb +482 -425
  19. data/lib/rack/media_type.rb +14 -10
  20. data/lib/rack/mime.rb +6 -5
  21. data/lib/rack/mock_request.rb +10 -15
  22. data/lib/rack/mock_response.rb +50 -20
  23. data/lib/rack/multipart/parser.rb +255 -76
  24. data/lib/rack/multipart/uploaded_file.rb +42 -5
  25. data/lib/rack/multipart.rb +34 -1
  26. data/lib/rack/query_parser.rb +86 -78
  27. data/lib/rack/request.rb +78 -65
  28. data/lib/rack/response.rb +28 -20
  29. data/lib/rack/rewindable_input.rb +4 -1
  30. data/lib/rack/sendfile.rb +51 -21
  31. data/lib/rack/show_exceptions.rb +10 -4
  32. data/lib/rack/show_status.rb +0 -2
  33. data/lib/rack/static.rb +7 -3
  34. data/lib/rack/utils.rb +175 -119
  35. data/lib/rack/version.rb +3 -20
  36. data/lib/rack.rb +1 -4
  37. metadata +6 -12
  38. data/lib/rack/auth/digest/md5.rb +0 -1
  39. data/lib/rack/auth/digest/nonce.rb +0 -1
  40. data/lib/rack/auth/digest/params.rb +0 -1
  41. data/lib/rack/auth/digest/request.rb +0 -1
  42. data/lib/rack/auth/digest.rb +0 -256
  43. data/lib/rack/chunked.rb +0 -120
  44. data/lib/rack/file.rb +0 -9
  45. data/lib/rack/logger.rb +0 -22
data/lib/rack/builder.rb CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  require_relative 'urlmap'
4
4
 
5
+ module Rack; end
6
+ Rack::BUILDER_TOPLEVEL_BINDING = ->(builder){builder.instance_eval{binding}}
7
+
5
8
  module Rack
6
9
  # Rack::Builder provides a domain-specific language (DSL) to construct Rack
7
10
  # applications. It is primarily used to parse +config.ru+ files which
@@ -53,15 +56,15 @@ module Rack
53
56
  # Rack::Builder.parse_file('app.rb')
54
57
  # # requires app.rb, which can be anywhere in Ruby's
55
58
  # # load path. After requiring, assumes App constant
56
- # # contains Rack application
59
+ # # is a Rack application
57
60
  #
58
61
  # Rack::Builder.parse_file('./my_app.rb')
59
62
  # # requires ./my_app.rb, which should be in the
60
63
  # # process's current directory. After requiring,
61
- # # assumes MyApp constant contains Rack application
62
- def self.parse_file(path)
64
+ # # assumes MyApp constant is a Rack application
65
+ def self.parse_file(path, **options)
63
66
  if path.end_with?('.ru')
64
- return self.load_file(path)
67
+ return self.load_file(path, **options)
65
68
  else
66
69
  require path
67
70
  return Object.const_get(::File.basename(path, '.rb').split('_').map(&:capitalize).join(''))
@@ -81,7 +84,7 @@ module Rack
81
84
  # use Rack::ContentLength
82
85
  # require './app.rb'
83
86
  # run App
84
- def self.load_file(path)
87
+ def self.load_file(path, **options)
85
88
  config = ::File.read(path)
86
89
  config.slice!(/\A#{UTF_8_BOM}/) if config.encoding == Encoding::UTF_8
87
90
 
@@ -91,16 +94,18 @@ module Rack
91
94
 
92
95
  config.sub!(/^__END__\n.*\Z/m, '')
93
96
 
94
- return new_from_string(config, path)
97
+ return new_from_string(config, path, **options)
95
98
  end
96
99
 
97
100
  # Evaluate the given +builder_script+ string in the context of
98
101
  # a Rack::Builder block, returning a Rack application.
99
- def self.new_from_string(builder_script, file = "(rackup)")
102
+ def self.new_from_string(builder_script, path = "(rackup)", **options)
103
+ builder = self.new(**options)
104
+
100
105
  # We want to build a variant of TOPLEVEL_BINDING with self as a Rack::Builder instance.
101
106
  # We cannot use instance_eval(String) as that would resolve constants differently.
102
- binding, builder = TOPLEVEL_BINDING.eval('Rack::Builder.new.instance_eval { [binding, self] }')
103
- eval builder_script, binding, file
107
+ binding = BUILDER_TOPLEVEL_BINDING.call(builder)
108
+ eval(builder_script, binding, path)
104
109
 
105
110
  return builder.to_app
106
111
  end
@@ -108,16 +113,24 @@ module Rack
108
113
  # Initialize a new Rack::Builder instance. +default_app+ specifies the
109
114
  # default application if +run+ is not called later. If a block
110
115
  # is given, it is evaluated in the context of the instance.
111
- def initialize(default_app = nil, &block)
116
+ def initialize(default_app = nil, **options, &block)
112
117
  @use = []
113
118
  @map = nil
114
119
  @run = default_app
115
120
  @warmup = nil
116
121
  @freeze_app = false
122
+ @options = options
117
123
 
118
124
  instance_eval(&block) if block_given?
119
125
  end
120
126
 
127
+ # Any options provided to the Rack::Builder instance at initialization.
128
+ # These options can be server-specific. Some general options are:
129
+ #
130
+ # * +:isolation+: One of +process+, +thread+ or +fiber+. The execution
131
+ # isolation model to use.
132
+ attr :options
133
+
121
134
  # Create a new Rack::Builder instance and return the Rack application
122
135
  # generated from it.
123
136
  def self.app(default_app = nil, &block)
@@ -149,6 +162,8 @@ module Rack
149
162
  @use << proc { |app| generate_map(app, mapping) }
150
163
  end
151
164
  @use << proc { |app| middleware.new(app, *args, &block) }
165
+
166
+ nil
152
167
  end
153
168
  # :nocov:
154
169
  ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
@@ -181,6 +196,8 @@ module Rack
181
196
  raise ArgumentError, "Both app and block given!" if app && block_given?
182
197
 
183
198
  @run = app || block
199
+
200
+ nil
184
201
  end
185
202
 
186
203
  # Takes a lambda or block that is used to warm-up the application. This block is called
@@ -239,6 +256,8 @@ module Rack
239
256
  def map(path, &block)
240
257
  @map ||= {}
241
258
  @map[path] = block
259
+
260
+ nil
242
261
  end
243
262
 
244
263
  # Freeze the app (set using run) and all middleware instances when building the application
data/lib/rack/cascade.rb CHANGED
@@ -9,9 +9,6 @@ module Rack
9
9
  # status codes, return the last response.
10
10
 
11
11
  class Cascade
12
- # deprecated, no longer used
13
- NotFound = [404, { CONTENT_TYPE => "text/plain" }, []]
14
-
15
12
  # An array of applications to try in order.
16
13
  attr_reader :apps
17
14
 
@@ -34,9 +34,10 @@ module Rack
34
34
  response[0] = 304
35
35
  headers.delete(CONTENT_TYPE)
36
36
  headers.delete(CONTENT_LENGTH)
37
- response[2] = Rack::BodyProxy.new([]) do
38
- body.close if body.respond_to?(:close)
39
- end
37
+
38
+ # We are done with the body:
39
+ body.close if body.respond_to?(:close)
40
+ response[2] = []
40
41
  end
41
42
  response
42
43
  else
@@ -32,6 +32,7 @@ module Rack
32
32
  DELETE = 'DELETE'
33
33
  HEAD = 'HEAD'
34
34
  OPTIONS = 'OPTIONS'
35
+ CONNECT = 'CONNECT'
35
36
  LINK = 'LINK'
36
37
  UNLINK = 'UNLINK'
37
38
  TRACE = 'TRACE'
@@ -39,6 +40,7 @@ module Rack
39
40
  # Rack environment variables
40
41
  RACK_VERSION = 'rack.version'
41
42
  RACK_TEMPFILES = 'rack.tempfiles'
43
+ RACK_EARLY_HINTS = 'rack.early_hints'
42
44
  RACK_ERRORS = 'rack.errors'
43
45
  RACK_LOGGER = 'rack.logger'
44
46
  RACK_INPUT = 'rack.input'
@@ -52,8 +54,10 @@ module Rack
52
54
  RACK_MULTIPART_BUFFER_SIZE = 'rack.multipart.buffer_size'
53
55
  RACK_MULTIPART_TEMPFILE_FACTORY = 'rack.multipart.tempfile_factory'
54
56
  RACK_RESPONSE_FINISHED = 'rack.response_finished'
57
+ RACK_PROTOCOL = 'rack.protocol'
55
58
  RACK_REQUEST_FORM_INPUT = 'rack.request.form_input'
56
59
  RACK_REQUEST_FORM_HASH = 'rack.request.form_hash'
60
+ RACK_REQUEST_FORM_PAIRS = 'rack.request.form_pairs'
57
61
  RACK_REQUEST_FORM_VARS = 'rack.request.form_vars'
58
62
  RACK_REQUEST_FORM_ERROR = 'rack.request.form_error'
59
63
  RACK_REQUEST_COOKIE_HASH = 'rack.request.cookie_hash'
@@ -17,7 +17,7 @@ module Rack
17
17
  # If +app+ is not specified, a Rack::Files of the same +root+ will be used.
18
18
 
19
19
  class Directory
20
- DIR_FILE = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>\n"
20
+ DIR_FILE = "<tr><td class='name'><a href='./%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>\n"
21
21
  DIR_PAGE_HEADER = <<-PAGE
22
22
  <html><head>
23
23
  <title>%s</title>
@@ -51,7 +51,7 @@ table { width:100%%; }
51
51
  class DirectoryBody < Struct.new(:root, :path, :files)
52
52
  # Yield strings for each part of the directory entry
53
53
  def each
54
- show_path = Utils.escape_html(path.sub(/^#{root}/, ''))
54
+ show_path = Utils.escape_html(path.sub(/\A#{Regexp.escape(root)}/, ''))
55
55
  yield(DIR_PAGE_HEADER % [ show_path, show_path ])
56
56
 
57
57
  unless path.chomp('/') == root
@@ -82,6 +82,7 @@ table { width:100%%; }
82
82
  # Set the root directory and application for serving files.
83
83
  def initialize(root, app = nil)
84
84
  @root = ::File.expand_path(root)
85
+ @root_with_separator = @root.end_with?(::File::SEPARATOR) ? @root : "#{@root}#{::File::SEPARATOR}"
85
86
  @app = app || Files.new(@root)
86
87
  @head = Head.new(method(:get))
87
88
  end
@@ -118,7 +119,9 @@ table { width:100%%; }
118
119
  # Rack response to use for requests with paths outside the root, or nil if path is inside the root.
119
120
  def check_forbidden(path_info)
120
121
  return unless path_info.include? ".."
121
- return if ::File.expand_path(::File.join(@root, path_info)).start_with?(@root)
122
+
123
+ expanded_path = ::File.expand_path(::File.join(@root, path_info))
124
+ return if expanded_path == @root || expanded_path.start_with?(@root_with_separator)
122
125
 
123
126
  body = "Forbidden\n"
124
127
  [403, { CONTENT_TYPE => "text/plain",
data/lib/rack/events.rb CHANGED
@@ -29,12 +29,13 @@ module Rack
29
29
  #
30
30
  # * on_send(request, response)
31
31
  #
32
- # The webserver has started iterating over the response body and presumably
33
- # has started sending data over the wire. This method is always called with
34
- # a request object and the response object. The response object is
35
- # constructed from the rack triple that the application returned. Changes
36
- # SHOULD NOT be made to the response object as the webserver has already
37
- # started sending data. Any mutations will likely result in an exception.
32
+ # The webserver has started iterating over the response body, or has called
33
+ # the streaming body, and presumably has started sending data over the
34
+ # wire. This method is always called with a request object and the response
35
+ # object. The response object is constructed from the rack triple that the
36
+ # application returned. Changes SHOULD NOT be made to the response object
37
+ # as the webserver has already started sending data. Any mutations will
38
+ # likely result in an exception.
38
39
  #
39
40
  # * on_finish(request, response)
40
41
  #
@@ -90,6 +91,20 @@ module Rack
90
91
  @handlers.reverse_each { |handler| handler.on_send request, response }
91
92
  super
92
93
  end
94
+
95
+ def call(stream)
96
+ @handlers.reverse_each { |handler| handler.on_send request, response }
97
+ super
98
+ end
99
+
100
+ def respond_to?(method_name, include_all = false)
101
+ case method_name
102
+ when :each, :call
103
+ @body.respond_to?(method_name, include_all)
104
+ else
105
+ super
106
+ end
107
+ end
93
108
  end
94
109
 
95
110
  class BufferedResponse < Rack::Response::Raw # :nodoc:
data/lib/rack/files.rb CHANGED
@@ -194,7 +194,7 @@ EOF
194
194
  status,
195
195
  {
196
196
  CONTENT_TYPE => "text/plain",
197
- CONTENT_LENGTH => body.size.to_s,
197
+ CONTENT_LENGTH => body.bytesize.to_s,
198
198
  "x-cascade" => "pass"
199
199
  }.merge!(headers),
200
200
  [body]
data/lib/rack/head.rb CHANGED
@@ -15,9 +15,8 @@ module Rack
15
15
  _, _, body = response = @app.call(env)
16
16
 
17
17
  if env[REQUEST_METHOD] == HEAD
18
- response[2] = Rack::BodyProxy.new([]) do
19
- body.close if body.respond_to? :close
20
- end
18
+ body.close if body.respond_to?(:close)
19
+ response[2] = []
21
20
  end
22
21
 
23
22
  response
data/lib/rack/headers.rb CHANGED
@@ -1,9 +1,93 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Rack::Headers is a Hash subclass that downcases all keys. It's designed
3
5
  # to be used by rack applications that don't implement the Rack 3 SPEC
4
6
  # (by using non-lowercase response header keys), automatically handling
5
7
  # the downcasing of keys.
6
8
  class Headers < Hash
9
+ KNOWN_HEADERS = {}
10
+ %w(
11
+ Accept-CH
12
+ Accept-Patch
13
+ Accept-Ranges
14
+ Access-Control-Allow-Credentials
15
+ Access-Control-Allow-Headers
16
+ Access-Control-Allow-Methods
17
+ Access-Control-Allow-Origin
18
+ Access-Control-Expose-Headers
19
+ Access-Control-Max-Age
20
+ Age
21
+ Allow
22
+ Alt-Svc
23
+ Cache-Control
24
+ Connection
25
+ Content-Disposition
26
+ Content-Encoding
27
+ Content-Language
28
+ Content-Length
29
+ Content-Location
30
+ Content-MD5
31
+ Content-Range
32
+ Content-Security-Policy
33
+ Content-Security-Policy-Report-Only
34
+ Content-Type
35
+ Date
36
+ Delta-Base
37
+ ETag
38
+ Expect-CT
39
+ Expires
40
+ Feature-Policy
41
+ IM
42
+ Last-Modified
43
+ Link
44
+ Location
45
+ NEL
46
+ P3P
47
+ Permissions-Policy
48
+ Pragma
49
+ Preference-Applied
50
+ Proxy-Authenticate
51
+ Public-Key-Pins
52
+ Referrer-Policy
53
+ Refresh
54
+ Report-To
55
+ Retry-After
56
+ Server
57
+ Set-Cookie
58
+ Status
59
+ Strict-Transport-Security
60
+ Timing-Allow-Origin
61
+ Tk
62
+ Trailer
63
+ Transfer-Encoding
64
+ Upgrade
65
+ Vary
66
+ Via
67
+ WWW-Authenticate
68
+ Warning
69
+ X-Cascade
70
+ X-Content-Duration
71
+ X-Content-Security-Policy
72
+ X-Content-Type-Options
73
+ X-Correlation-ID
74
+ X-Correlation-Id
75
+ X-Download-Options
76
+ X-Frame-Options
77
+ X-Permitted-Cross-Domain-Policies
78
+ X-Powered-By
79
+ X-Redirect-By
80
+ X-Request-ID
81
+ X-Request-Id
82
+ X-Runtime
83
+ X-UA-Compatible
84
+ X-WebKit-CS
85
+ X-XSS-Protection
86
+ ).each do |str|
87
+ downcased = str.downcase.freeze
88
+ KNOWN_HEADERS[str] = KNOWN_HEADERS[downcased] = downcased
89
+ end
90
+
7
91
  def self.[](*items)
8
92
  if items.length % 2 != 0
9
93
  if items.length == 1 && items.first.is_a?(Hash)
@@ -28,7 +112,7 @@ module Rack
28
112
  end
29
113
 
30
114
  def []=(key, value)
31
- super(key.downcase.freeze, value)
115
+ super(KNOWN_HEADERS[key] || key.downcase.freeze, value)
32
116
  end
33
117
  alias store []=
34
118
 
@@ -148,7 +232,7 @@ module Rack
148
232
  private
149
233
 
150
234
  def downcase_key(key)
151
- key.is_a?(String) ? key.downcase : key
235
+ key.is_a?(String) ? KNOWN_HEADERS[key] || key.downcase : key
152
236
  end
153
237
  end
154
238
  end