rack 2.2.17 → 3.0.18

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.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +292 -74
  3. data/CONTRIBUTING.md +53 -47
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +336 -0
  6. data/SPEC.rdoc +174 -126
  7. data/lib/rack/auth/abstract/handler.rb +3 -1
  8. data/lib/rack/auth/abstract/request.rb +3 -1
  9. data/lib/rack/auth/basic.rb +2 -3
  10. data/lib/rack/auth/digest/md5.rb +1 -131
  11. data/lib/rack/auth/digest/nonce.rb +1 -53
  12. data/lib/rack/auth/digest/params.rb +1 -54
  13. data/lib/rack/auth/digest/request.rb +1 -43
  14. data/lib/rack/auth/digest.rb +256 -0
  15. data/lib/rack/body_proxy.rb +21 -3
  16. data/lib/rack/builder.rb +83 -63
  17. data/lib/rack/cascade.rb +2 -0
  18. data/lib/rack/chunked.rb +16 -13
  19. data/lib/rack/common_logger.rb +22 -17
  20. data/lib/rack/conditional_get.rb +18 -15
  21. data/lib/rack/constants.rb +64 -0
  22. data/lib/rack/content_length.rb +12 -16
  23. data/lib/rack/content_type.rb +8 -5
  24. data/lib/rack/deflater.rb +40 -26
  25. data/lib/rack/directory.rb +9 -3
  26. data/lib/rack/etag.rb +17 -23
  27. data/lib/rack/events.rb +4 -0
  28. data/lib/rack/file.rb +2 -0
  29. data/lib/rack/files.rb +15 -17
  30. data/lib/rack/head.rb +9 -8
  31. data/lib/rack/headers.rb +154 -0
  32. data/lib/rack/lint.rb +758 -646
  33. data/lib/rack/lock.rb +2 -5
  34. data/lib/rack/logger.rb +2 -0
  35. data/lib/rack/media_type.rb +3 -8
  36. data/lib/rack/method_override.rb +5 -1
  37. data/lib/rack/mime.rb +8 -0
  38. data/lib/rack/mock.rb +1 -300
  39. data/lib/rack/mock_request.rb +166 -0
  40. data/lib/rack/mock_response.rb +155 -0
  41. data/lib/rack/multipart/generator.rb +7 -5
  42. data/lib/rack/multipart/parser.rb +127 -69
  43. data/lib/rack/multipart/uploaded_file.rb +4 -0
  44. data/lib/rack/multipart.rb +20 -40
  45. data/lib/rack/null_logger.rb +9 -0
  46. data/lib/rack/query_parser.rb +78 -46
  47. data/lib/rack/recursive.rb +2 -0
  48. data/lib/rack/reloader.rb +0 -2
  49. data/lib/rack/request.rb +224 -106
  50. data/lib/rack/response.rb +138 -61
  51. data/lib/rack/rewindable_input.rb +24 -5
  52. data/lib/rack/runtime.rb +7 -6
  53. data/lib/rack/sendfile.rb +30 -25
  54. data/lib/rack/show_exceptions.rb +15 -2
  55. data/lib/rack/show_status.rb +17 -7
  56. data/lib/rack/static.rb +8 -8
  57. data/lib/rack/tempfile_reaper.rb +15 -4
  58. data/lib/rack/urlmap.rb +3 -1
  59. data/lib/rack/utils.rb +206 -180
  60. data/lib/rack/version.rb +9 -4
  61. data/lib/rack.rb +13 -87
  62. metadata +15 -35
  63. data/README.rdoc +0 -347
  64. data/Rakefile +0 -130
  65. data/bin/rackup +0 -5
  66. data/contrib/rack.png +0 -0
  67. data/contrib/rack.svg +0 -150
  68. data/contrib/rack_logo.svg +0 -164
  69. data/contrib/rdoc.css +0 -412
  70. data/example/lobster.ru +0 -6
  71. data/example/protectedlobster.rb +0 -16
  72. data/example/protectedlobster.ru +0 -10
  73. data/lib/rack/core_ext/regexp.rb +0 -14
  74. data/lib/rack/handler/cgi.rb +0 -59
  75. data/lib/rack/handler/fastcgi.rb +0 -100
  76. data/lib/rack/handler/lsws.rb +0 -61
  77. data/lib/rack/handler/scgi.rb +0 -71
  78. data/lib/rack/handler/thin.rb +0 -36
  79. data/lib/rack/handler/webrick.rb +0 -129
  80. data/lib/rack/handler.rb +0 -104
  81. data/lib/rack/lobster.rb +0 -70
  82. data/lib/rack/server.rb +0 -466
  83. data/lib/rack/session/abstract/id.rb +0 -523
  84. data/lib/rack/session/cookie.rb +0 -203
  85. data/lib/rack/session/memcache.rb +0 -10
  86. data/lib/rack/session/pool.rb +0 -90
  87. data/rack.gemspec +0 -46
data/lib/rack/static.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'files'
5
+ require_relative 'mime'
6
+
3
7
  module Rack
4
8
 
5
9
  # The Rack::Static middleware intercepts requests for static files
@@ -78,16 +82,14 @@ module Rack
78
82
  # :header_rules => [
79
83
  # # Cache all static files in public caches (e.g. Rack::Cache)
80
84
  # # as well as in the browser
81
- # [:all, {'Cache-Control' => 'public, max-age=31536000'}],
85
+ # [:all, {'cache-control' => 'public, max-age=31536000'}],
82
86
  #
83
87
  # # Provide web fonts with cross-origin access-control-headers
84
88
  # # Firefox requires this when serving assets using a Content Delivery Network
85
- # [:fonts, {'Access-Control-Allow-Origin' => '*'}]
89
+ # [:fonts, {'access-control-allow-origin' => '*'}]
86
90
  # ]
87
91
  #
88
92
  class Static
89
- (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
90
-
91
93
  def initialize(app, options = {})
92
94
  @app = app
93
95
  @urls = options[:urls] || ["/favicon.ico"]
@@ -138,10 +140,8 @@ module Rack
138
140
  elsif response[0] == 304
139
141
  # Do nothing, leave headers as is
140
142
  else
141
- if mime_type = Mime.mime_type(::File.extname(path), 'text/plain')
142
- response[1][CONTENT_TYPE] = mime_type
143
- end
144
- response[1]['Content-Encoding'] = 'gzip'
143
+ response[1][CONTENT_TYPE] = Mime.mime_type(::File.extname(path), 'text/plain')
144
+ response[1]['content-encoding'] = 'gzip'
145
145
  end
146
146
  end
147
147
 
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'body_proxy'
5
+
3
6
  module Rack
4
7
 
5
8
  # Middleware tracks and cleans Tempfiles created throughout a request (i.e. Rack::Multipart)
@@ -12,11 +15,19 @@ module Rack
12
15
 
13
16
  def call(env)
14
17
  env[RACK_TEMPFILES] ||= []
15
- status, headers, body = @app.call(env)
16
- body_proxy = BodyProxy.new(body) do
17
- env[RACK_TEMPFILES].each(&:close!) unless env[RACK_TEMPFILES].nil?
18
+
19
+ begin
20
+ _, _, body = response = @app.call(env)
21
+ rescue Exception
22
+ env[RACK_TEMPFILES]&.each(&:close!)
23
+ raise
18
24
  end
19
- [status, headers, body_proxy]
25
+
26
+ response[2] = BodyProxy.new(body) do
27
+ env[RACK_TEMPFILES]&.each(&:close!)
28
+ end
29
+
30
+ response
20
31
  end
21
32
  end
22
33
  end
data/lib/rack/urlmap.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'set'
4
4
 
5
+ require_relative 'constants'
6
+
5
7
  module Rack
6
8
  # Rack::URLMap takes a hash mapping urls or paths to apps, and
7
9
  # dispatches accordingly. Support for HTTP/1.1 host names exists if
@@ -74,7 +76,7 @@ module Rack
74
76
  return app.call(env)
75
77
  end
76
78
 
77
- [404, { CONTENT_TYPE => "text/plain", "X-Cascade" => "pass" }, ["Not Found: #{path}"]]
79
+ [404, { CONTENT_TYPE => "text/plain", "x-cascade" => "pass" }, ["Not Found: #{path}"]]
78
80
 
79
81
  ensure
80
82
  env[PATH_INFO] = path
data/lib/rack/utils.rb CHANGED
@@ -8,30 +8,29 @@ require 'tempfile'
8
8
  require 'time'
9
9
 
10
10
  require_relative 'query_parser'
11
+ require_relative 'mime'
12
+ require_relative 'headers'
13
+ require_relative 'constants'
11
14
 
12
15
  module Rack
13
16
  # Rack::Utils contains a grab-bag of useful methods for writing web
14
17
  # applications adopted from all kinds of Ruby libraries.
15
18
 
16
19
  module Utils
17
- (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
18
-
19
20
  ParameterTypeError = QueryParser::ParameterTypeError
20
21
  InvalidParameterError = QueryParser::InvalidParameterError
22
+ ParamsTooDeepError = QueryParser::ParamsTooDeepError
21
23
  DEFAULT_SEP = QueryParser::DEFAULT_SEP
22
24
  COMMON_SEP = QueryParser::COMMON_SEP
23
25
  KeySpaceConstrainedParams = QueryParser::Params
24
26
 
25
- RFC2822_DAY_NAME = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]
26
- RFC2822_MONTH_NAME = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
27
- RFC2396_PARSER = defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::RFC2396_Parser.new
28
-
29
27
  class << self
30
28
  attr_accessor :default_query_parser
31
29
  end
32
- # The default number of bytes to allow parameter keys to take up.
33
- # This helps prevent a rogue client from flooding a Request.
34
- self.default_query_parser = QueryParser.make_default(65536, 100)
30
+ # The default amount of nesting to allowed by hash parameters.
31
+ # This helps prevent a rogue client from triggering a possible stack overflow
32
+ # when parsing parameters.
33
+ self.default_query_parser = QueryParser.make_default(32)
35
34
 
36
35
  module_function
37
36
 
@@ -43,13 +42,13 @@ module Rack
43
42
  # Like URI escaping, but with %20 instead of +. Strictly speaking this is
44
43
  # true URI escaping.
45
44
  def escape_path(s)
46
- RFC2396_PARSER.escape s
45
+ ::URI::DEFAULT_PARSER.escape s
47
46
  end
48
47
 
49
48
  # Unescapes the **path** component of a URI. See Rack::Utils.unescape for
50
49
  # unescaping query parameters or form components.
51
50
  def unescape_path(s)
52
- RFC2396_PARSER.unescape s
51
+ ::URI::DEFAULT_PARSER.unescape s
53
52
  end
54
53
 
55
54
  # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
@@ -87,11 +86,12 @@ module Rack
87
86
  end
88
87
 
89
88
  def self.key_space_limit
90
- default_query_parser.key_space_limit
89
+ warn("`Rack::Utils.key_space_limit` is deprecated as this value no longer has an effect. It will be removed in Rack 3.1", uplevel: 1)
90
+ 65536
91
91
  end
92
92
 
93
93
  def self.key_space_limit=(v)
94
- self.default_query_parser = self.default_query_parser.new_space_limit(v)
94
+ warn("`Rack::Utils.key_space_limit=` is deprecated and no longer has an effect. It will be removed in Rack 3.1", uplevel: 1)
95
95
  end
96
96
 
97
97
  if defined?(Process::CLOCK_MONOTONIC)
@@ -132,13 +132,13 @@ module Rack
132
132
  }.join("&")
133
133
  when Hash
134
134
  value.map { |k, v|
135
- build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
135
+ build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
136
136
  }.delete_if(&:empty?).join('&')
137
137
  when nil
138
- prefix
138
+ escape(prefix)
139
139
  else
140
140
  raise ArgumentError, "value must be a Hash" if prefix.nil?
141
- "#{prefix}=#{escape(value)}"
141
+ "#{escape(prefix)}=#{escape(value)}"
142
142
  end
143
143
  end
144
144
 
@@ -153,6 +153,20 @@ module Rack
153
153
  end
154
154
  end
155
155
 
156
+ def forwarded_values(forwarded_header)
157
+ return nil unless forwarded_header
158
+ forwarded_header = forwarded_header.to_s.gsub("\n", ";")
159
+
160
+ forwarded_header.split(';').each_with_object({}) do |field, values|
161
+ field.split(',').each do |pair|
162
+ pair = pair.split('=').map(&:strip).join('=')
163
+ return nil unless pair =~ /\A(by|for|host|proto)="?([^"]+)"?\Z/i
164
+ (values[$1.downcase.to_sym] ||= []) << $2
165
+ end
166
+ end
167
+ end
168
+ module_function :forwarded_values
169
+
156
170
  # Return best accept value to use, based on the algorithm
157
171
  # in RFC 2616 Section 14. If there are multiple best
158
172
  # matches (same specificity and quality), the value returned
@@ -167,7 +181,7 @@ module Rack
167
181
  end.compact.sort_by do |match, quality|
168
182
  (match.split('/', 2).count('*') * -10) + quality
169
183
  end.last
170
- matches && matches.first
184
+ matches&.first
171
185
  end
172
186
 
173
187
  ESCAPE_HTML = {
@@ -218,17 +232,20 @@ module Rack
218
232
  (encoding_candidates & available_encodings)[0]
219
233
  end
220
234
 
221
- def parse_cookies(env)
222
- parse_cookies_header env[HTTP_COOKIE]
223
- end
235
+ # :call-seq:
236
+ # parse_cookies_header(value) -> hash
237
+ #
238
+ # Parse cookies from the provided header +value+ according to RFC6265. The
239
+ # syntax for cookie headers only supports semicolons. Returns a map of
240
+ # cookie +key+ to cookie +value+.
241
+ #
242
+ # parse_cookies_header('myname=myvalue; max-age=0')
243
+ # # => {"myname"=>"myvalue", "max-age"=>"0"}
244
+ #
245
+ def parse_cookies_header(value)
246
+ return {} unless value
224
247
 
225
- def parse_cookies_header(header)
226
- # According to RFC 6265:
227
- # The syntax for cookie headers only supports semicolons
228
- # User Agent -> Server ==
229
- # Cookie: SID=31d4d96e407aad42; lang=en-US
230
- return {} unless header
231
- header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
248
+ value.split(/; */n).each_with_object({}) do |cookie, cookies|
232
249
  next if cookie.empty?
233
250
  key, value = cookie.split('=', 2)
234
251
  cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
@@ -236,14 +253,66 @@ module Rack
236
253
  end
237
254
 
238
255
  def add_cookie_to_header(header, key, value)
256
+ warn("add_cookie_to_header is deprecated and will be removed in Rack 3.1", uplevel: 1)
257
+
258
+ case header
259
+ when nil, ''
260
+ return set_cookie_header(key, value)
261
+ when String
262
+ [header, set_cookie_header(key, value)]
263
+ when Array
264
+ header + [set_cookie_header(key, value)]
265
+ else
266
+ raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
267
+ end
268
+ end
269
+
270
+ # :call-seq:
271
+ # parse_cookies(env) -> hash
272
+ #
273
+ # Parse cookies from the provided request environment using
274
+ # parse_cookies_header. Returns a map of cookie +key+ to cookie +value+.
275
+ #
276
+ # parse_cookies({'HTTP_COOKIE' => 'myname=myvalue'})
277
+ # # => {'myname' => 'myvalue'}
278
+ #
279
+ def parse_cookies(env)
280
+ parse_cookies_header env[HTTP_COOKIE]
281
+ end
282
+
283
+ # :call-seq:
284
+ # set_cookie_header(key, value) -> encoded string
285
+ #
286
+ # Generate an encoded string using the provided +key+ and +value+ suitable
287
+ # for the +set-cookie+ header according to RFC6265. The +value+ may be an
288
+ # instance of either +String+ or +Hash+.
289
+ #
290
+ # If the cookie +value+ is an instance of +Hash+, it considers the following
291
+ # cookie attribute keys: +domain+, +max_age+, +expires+ (must be instance
292
+ # of +Time+), +secure+, +http_only+, +same_site+ and +value+. For more
293
+ # details about the interpretation of these fields, consult
294
+ # [RFC6265 Section 5.2](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2).
295
+ #
296
+ # An extra cookie attribute +escape_key+ can be provided to control whether
297
+ # or not the cookie key is URL encoded. If explicitly set to +false+, the
298
+ # cookie key name will not be url encoded (escaped). The default is +true+.
299
+ #
300
+ # set_cookie_header("myname", "myvalue")
301
+ # # => "myname=myvalue"
302
+ #
303
+ # set_cookie_header("myname", {value: "myvalue", max_age: 10})
304
+ # # => "myname=myvalue; max-age=10"
305
+ #
306
+ def set_cookie_header(key, value)
239
307
  case value
240
308
  when Hash
309
+ key = escape(key) unless value[:escape_key] == false
241
310
  domain = "; domain=#{value[:domain]}" if value[:domain]
242
311
  path = "; path=#{value[:path]}" if value[:path]
243
312
  max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
244
313
  expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
245
314
  secure = "; secure" if value[:secure]
246
- httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
315
+ httponly = "; httponly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
247
316
  same_site =
248
317
  case value[:same_site]
249
318
  when false, nil
@@ -258,100 +327,109 @@ module Rack
258
327
  raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
259
328
  end
260
329
  value = value[:value]
330
+ else
331
+ key = escape(key)
261
332
  end
333
+
262
334
  value = [value] unless Array === value
263
335
 
264
- cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \
336
+ return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
265
337
  "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
338
+ end
266
339
 
267
- case header
268
- when nil, ''
269
- cookie
270
- when String
271
- [header, cookie].join("\n")
272
- when Array
273
- (header + [cookie]).join("\n")
340
+ # :call-seq:
341
+ # set_cookie_header!(headers, key, value) -> header value
342
+ #
343
+ # Append a cookie in the specified headers with the given cookie +key+ and
344
+ # +value+ using set_cookie_header.
345
+ #
346
+ # If the headers already contains a +set-cookie+ key, it will be converted
347
+ # to an +Array+ if not already, and appended to.
348
+ def set_cookie_header!(headers, key, value)
349
+ if header = headers[SET_COOKIE]
350
+ if header.is_a?(Array)
351
+ header << set_cookie_header(key, value)
352
+ else
353
+ headers[SET_COOKIE] = [header, set_cookie_header(key, value)]
354
+ end
274
355
  else
275
- raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
356
+ headers[SET_COOKIE] = set_cookie_header(key, value)
276
357
  end
277
358
  end
278
359
 
279
- def set_cookie_header!(header, key, value)
280
- header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
281
- nil
360
+ # :call-seq:
361
+ # delete_set_cookie_header(key, value = {}) -> encoded string
362
+ #
363
+ # Generate an encoded string based on the given +key+ and +value+ using
364
+ # set_cookie_header for the purpose of causing the specified cookie to be
365
+ # deleted. The +value+ may be an instance of +Hash+ and can include
366
+ # attributes as outlined by set_cookie_header. The encoded cookie will have
367
+ # a +max_age+ of 0 seconds, an +expires+ date in the past and an empty
368
+ # +value+. When used with the +set-cookie+ header, it will cause the client
369
+ # to *remove* any matching cookie.
370
+ #
371
+ # delete_set_cookie_header("myname")
372
+ # # => "myname=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
373
+ #
374
+ def delete_set_cookie_header(key, value = {})
375
+ set_cookie_header(key, value.merge(max_age: '0', expires: Time.at(0), value: ''))
282
376
  end
283
377
 
284
378
  def make_delete_cookie_header(header, key, value)
285
- case header
286
- when nil, ''
287
- cookies = []
288
- when String
289
- cookies = header.split("\n")
290
- when Array
291
- cookies = header
292
- end
293
-
294
- key = escape(key)
295
- domain = value[:domain]
296
- path = value[:path]
297
- regexp = if domain
298
- if path
299
- /\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
300
- else
301
- /\A#{key}=.*domain=#{domain}(?:;|$)/
302
- end
303
- elsif path
304
- /\A#{key}=.*path=#{path}(?:;|$)/
305
- else
306
- /\A#{key}=/
307
- end
308
-
309
- cookies.reject! { |cookie| regexp.match? cookie }
379
+ warn("make_delete_cookie_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
310
380
 
311
- cookies.join("\n")
381
+ delete_set_cookie_header!(header, key, value)
312
382
  end
313
383
 
314
- def delete_cookie_header!(header, key, value = {})
315
- header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
316
- nil
384
+ def delete_cookie_header!(headers, key, value = {})
385
+ headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value)
386
+
387
+ return nil
317
388
  end
318
389
 
319
- # Adds a cookie that will *remove* a cookie from the client. Hence the
320
- # strange method name.
321
390
  def add_remove_cookie_to_header(header, key, value = {})
322
- new_header = make_delete_cookie_header(header, key, value)
391
+ warn("add_remove_cookie_to_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
323
392
 
324
- add_cookie_to_header(new_header, key,
325
- { value: '', path: nil, domain: nil,
326
- max_age: '0',
327
- expires: Time.at(0) }.merge(value))
393
+ delete_set_cookie_header!(header, key, value)
394
+ end
328
395
 
396
+ # :call-seq:
397
+ # delete_set_cookie_header!(header, key, value = {}) -> header value
398
+ #
399
+ # Set an expired cookie in the specified headers with the given cookie
400
+ # +key+ and +value+ using delete_set_cookie_header. This causes
401
+ # the client to immediately delete the specified cookie.
402
+ #
403
+ # delete_set_cookie_header!(nil, "mycookie")
404
+ # # => "mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
405
+ #
406
+ # If the header is non-nil, it will be modified in place.
407
+ #
408
+ # header = []
409
+ # delete_set_cookie_header!(header, "mycookie")
410
+ # # => ["mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"]
411
+ # header
412
+ # # => ["mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"]
413
+ #
414
+ def delete_set_cookie_header!(header, key, value = {})
415
+ if header
416
+ header = Array(header)
417
+ header << delete_set_cookie_header(key, value)
418
+ else
419
+ header = delete_set_cookie_header(key, value)
420
+ end
421
+
422
+ return header
329
423
  end
330
424
 
331
425
  def rfc2822(time)
332
426
  time.rfc2822
333
427
  end
334
428
 
335
- # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
336
- # of '% %b %Y'.
337
- # It assumes that the time is in GMT to comply to the RFC 2109.
338
- #
339
- # NOTE: I'm not sure the RFC says it requires GMT, but is ambiguous enough
340
- # that I'm certain someone implemented only that option.
341
- # Do not use %a and %b from Time.strptime, it would use localized names for
342
- # weekday and month.
343
- #
344
- def rfc2109(time)
345
- wday = RFC2822_DAY_NAME[time.wday]
346
- mon = RFC2822_MONTH_NAME[time.mon - 1]
347
- time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
348
- end
349
-
350
429
  # Parses the "Range:" header, if present, into an array of Range objects.
351
430
  # Returns nil if the header is missing or syntactically invalid.
352
431
  # Returns an empty array if none of the ranges are satisfiable.
353
432
  def byte_ranges(env, size)
354
- warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
355
433
  get_byte_ranges env['HTTP_RANGE'], size
356
434
  end
357
435
 
@@ -382,25 +460,35 @@ module Rack
382
460
  ranges << (r0..r1) if r0 <= r1
383
461
  end
384
462
 
385
- return [] if ranges.map(&:size).inject(0, :+) > size
463
+ return [] if ranges.map(&:size).sum > size
386
464
 
387
465
  ranges
388
466
  end
389
467
 
390
- # Constant time string comparison.
391
- #
392
- # NOTE: the values compared should be of fixed length, such as strings
393
- # that have already been processed by HMAC. This should not be used
394
- # on variable length plaintext strings because it could leak length info
395
- # via timing attacks.
396
- def secure_compare(a, b)
397
- return false unless a.bytesize == b.bytesize
468
+ # :nocov:
469
+ if defined?(OpenSSL.fixed_length_secure_compare)
470
+ # Constant time string comparison.
471
+ #
472
+ # NOTE: the values compared should be of fixed length, such as strings
473
+ # that have already been processed by HMAC. This should not be used
474
+ # on variable length plaintext strings because it could leak length info
475
+ # via timing attacks.
476
+ def secure_compare(a, b)
477
+ return false unless a.bytesize == b.bytesize
478
+
479
+ OpenSSL.fixed_length_secure_compare(a, b)
480
+ end
481
+ # :nocov:
482
+ else
483
+ def secure_compare(a, b)
484
+ return false unless a.bytesize == b.bytesize
398
485
 
399
- l = a.unpack("C*")
486
+ l = a.unpack("C*")
400
487
 
401
- r, i = 0, -1
402
- b.each_byte { |v| r |= v ^ l[i += 1] }
403
- r == 0
488
+ r, i = 0, -1
489
+ b.each_byte { |v| r |= v ^ l[i += 1] }
490
+ r == 0
491
+ end
404
492
  end
405
493
 
406
494
  # Context allows the use of a compatible middleware at different points
@@ -429,94 +517,32 @@ module Rack
429
517
  end
430
518
  end
431
519
 
432
- # A case-insensitive Hash that preserves the original case of a
520
+ # A wrapper around Headers
433
521
  # header when set.
434
522
  #
435
523
  # @api private
436
524
  class HeaderHash < Hash # :nodoc:
437
525
  def self.[](headers)
438
- if headers.is_a?(HeaderHash) && !headers.frozen?
526
+ warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
527
+ if headers.is_a?(Headers) && !headers.frozen?
439
528
  return headers
440
- else
441
- return self.new(headers)
442
529
  end
443
- end
444
530
 
445
- def initialize(hash = {})
446
- super()
447
- @names = {}
448
- hash.each { |k, v| self[k] = v }
531
+ new_headers = Headers.new
532
+ headers.each{|k,v| new_headers[k] = v}
533
+ new_headers
449
534
  end
450
535
 
451
- # on dup/clone, we need to duplicate @names hash
452
- def initialize_copy(other)
453
- super
454
- @names = other.names.dup
536
+ def self.new(hash = {})
537
+ warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
538
+ headers = Headers.new
539
+ hash.each{|k,v| headers[k] = v}
540
+ headers
455
541
  end
456
542
 
457
- # on clear, we need to clear @names hash
458
- def clear
459
- super
460
- @names.clear
543
+ def self.allocate
544
+ raise TypeError, "cannot allocate HeaderHash"
461
545
  end
462
-
463
- def each
464
- super do |k, v|
465
- yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
466
- end
467
- end
468
-
469
- def to_hash
470
- hash = {}
471
- each { |k, v| hash[k] = v }
472
- hash
473
- end
474
-
475
- def [](k)
476
- super(k) || super(@names[k.downcase])
477
- end
478
-
479
- def []=(k, v)
480
- canonical = k.downcase.freeze
481
- delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
482
- @names[canonical] = k
483
- super k, v
484
- end
485
-
486
- def delete(k)
487
- canonical = k.downcase
488
- result = super @names.delete(canonical)
489
- result
490
- end
491
-
492
- def include?(k)
493
- super || @names.include?(k.downcase)
494
- end
495
-
496
- alias_method :has_key?, :include?
497
- alias_method :member?, :include?
498
- alias_method :key?, :include?
499
-
500
- def merge!(other)
501
- other.each { |k, v| self[k] = v }
502
- self
503
- end
504
-
505
- def merge(other)
506
- hash = dup
507
- hash.merge! other
508
- end
509
-
510
- def replace(other)
511
- clear
512
- other.each { |k, v| self[k] = v }
513
- self
514
- end
515
-
516
- protected
517
- def names
518
- @names
519
- end
520
546
  end
521
547
 
522
548
  # Every standard HTTP code mapped to the appropriate message.
data/lib/rack/version.rb CHANGED
@@ -13,14 +13,19 @@
13
13
 
14
14
  module Rack
15
15
  # The Rack protocol version number implemented.
16
- VERSION = [1, 3]
16
+ VERSION = [1, 3].freeze
17
+ deprecate_constant :VERSION
17
18
 
18
- # Return the Rack protocol version as a dotted string.
19
+ VERSION_STRING = "1.3".freeze
20
+ deprecate_constant :VERSION_STRING
21
+
22
+ # The Rack protocol version number implemented.
19
23
  def self.version
20
- VERSION.join(".")
24
+ warn "Rack.version is deprecated and will be removed in Rack 3.1!", uplevel: 1
25
+ VERSION
21
26
  end
22
27
 
23
- RELEASE = "2.2.17"
28
+ RELEASE = "3.0.18"
24
29
 
25
30
  # Return the Rack release as a dotted string.
26
31
  def self.release