rack 2.2.8 → 3.0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +213 -83
  3. data/CONTRIBUTING.md +53 -47
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +309 -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 +0 -2
  10. data/lib/rack/auth/digest/md5.rb +1 -131
  11. data/lib/rack/auth/digest/nonce.rb +1 -54
  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 +3 -1
  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 +23 -18
  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 +14 -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 +9 -4
  36. data/lib/rack/method_override.rb +5 -1
  37. data/lib/rack/mime.rb +8 -0
  38. data/lib/rack/mock.rb +1 -271
  39. data/lib/rack/mock_request.rb +166 -0
  40. data/lib/rack/mock_response.rb +126 -0
  41. data/lib/rack/multipart/generator.rb +7 -5
  42. data/lib/rack/multipart/parser.rb +120 -64
  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 +208 -178
  60. data/lib/rack/version.rb +9 -4
  61. data/lib/rack.rb +6 -76
  62. metadata +14 -34
  63. data/README.rdoc +0 -320
  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 -204
  85. data/lib/rack/session/memcache.rb +0 -10
  86. data/lib/rack/session/pool.rb +0 -85
  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"]
@@ -137,10 +139,8 @@ module Rack
137
139
  elsif response[0] == 304
138
140
  # Do nothing, leave headers as is
139
141
  else
140
- if mime_type = Mime.mime_type(::File.extname(path), 'text/plain')
141
- response[1][CONTENT_TYPE] = mime_type
142
- end
143
- response[1]['Content-Encoding'] = 'gzip'
142
+ response[1][CONTENT_TYPE] = Mime.mime_type(::File.extname(path), 'text/plain')
143
+ response[1]['content-encoding'] = 'gzip'
144
144
  end
145
145
  end
146
146
 
@@ -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,29 +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
-
28
27
  class << self
29
28
  attr_accessor :default_query_parser
30
29
  end
31
- # The default number of bytes to allow parameter keys to take up.
32
- # This helps prevent a rogue client from flooding a Request.
33
- 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)
34
34
 
35
35
  module_function
36
36
 
@@ -86,11 +86,12 @@ module Rack
86
86
  end
87
87
 
88
88
  def self.key_space_limit
89
- 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
90
91
  end
91
92
 
92
93
  def self.key_space_limit=(v)
93
- 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)
94
95
  end
95
96
 
96
97
  if defined?(Process::CLOCK_MONOTONIC)
@@ -131,19 +132,19 @@ module Rack
131
132
  }.join("&")
132
133
  when Hash
133
134
  value.map { |k, v|
134
- build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
135
+ build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
135
136
  }.delete_if(&:empty?).join('&')
136
137
  when nil
137
- prefix
138
+ escape(prefix)
138
139
  else
139
140
  raise ArgumentError, "value must be a Hash" if prefix.nil?
140
- "#{prefix}=#{escape(value)}"
141
+ "#{escape(prefix)}=#{escape(value)}"
141
142
  end
142
143
  end
143
144
 
144
145
  def q_values(q_value_header)
145
- q_value_header.to_s.split(/\s*,\s*/).map do |part|
146
- value, parameters = part.split(/\s*;\s*/, 2)
146
+ q_value_header.to_s.split(',').map do |part|
147
+ value, parameters = part.split(';', 2).map(&:strip)
147
148
  quality = 1.0
148
149
  if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
149
150
  quality = md[1].to_f
@@ -152,6 +153,20 @@ module Rack
152
153
  end
153
154
  end
154
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
+
155
170
  # Return best accept value to use, based on the algorithm
156
171
  # in RFC 2616 Section 14. If there are multiple best
157
172
  # matches (same specificity and quality), the value returned
@@ -166,7 +181,7 @@ module Rack
166
181
  end.compact.sort_by do |match, quality|
167
182
  (match.split('/', 2).count('*') * -10) + quality
168
183
  end.last
169
- matches && matches.first
184
+ matches&.first
170
185
  end
171
186
 
172
187
  ESCAPE_HTML = {
@@ -217,17 +232,20 @@ module Rack
217
232
  (encoding_candidates & available_encodings)[0]
218
233
  end
219
234
 
220
- def parse_cookies(env)
221
- parse_cookies_header env[HTTP_COOKIE]
222
- 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
223
247
 
224
- def parse_cookies_header(header)
225
- # According to RFC 6265:
226
- # The syntax for cookie headers only supports semicolons
227
- # User Agent -> Server ==
228
- # Cookie: SID=31d4d96e407aad42; lang=en-US
229
- return {} unless header
230
- header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
248
+ value.split(/; */n).each_with_object({}) do |cookie, cookies|
231
249
  next if cookie.empty?
232
250
  key, value = cookie.split('=', 2)
233
251
  cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
@@ -235,14 +253,66 @@ module Rack
235
253
  end
236
254
 
237
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)
238
307
  case value
239
308
  when Hash
309
+ key = escape(key) unless value[:escape_key] == false
240
310
  domain = "; domain=#{value[:domain]}" if value[:domain]
241
311
  path = "; path=#{value[:path]}" if value[:path]
242
312
  max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
243
313
  expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
244
314
  secure = "; secure" if value[:secure]
245
- httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
315
+ httponly = "; httponly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
246
316
  same_site =
247
317
  case value[:same_site]
248
318
  when false, nil
@@ -257,100 +327,109 @@ module Rack
257
327
  raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
258
328
  end
259
329
  value = value[:value]
330
+ else
331
+ key = escape(key)
260
332
  end
333
+
261
334
  value = [value] unless Array === value
262
335
 
263
- cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \
336
+ return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
264
337
  "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
338
+ end
265
339
 
266
- case header
267
- when nil, ''
268
- cookie
269
- when String
270
- [header, cookie].join("\n")
271
- when Array
272
- (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
273
355
  else
274
- raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
356
+ headers[SET_COOKIE] = set_cookie_header(key, value)
275
357
  end
276
358
  end
277
359
 
278
- def set_cookie_header!(header, key, value)
279
- header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
280
- 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: ''))
281
376
  end
282
377
 
283
378
  def make_delete_cookie_header(header, key, value)
284
- case header
285
- when nil, ''
286
- cookies = []
287
- when String
288
- cookies = header.split("\n")
289
- when Array
290
- cookies = header
291
- end
379
+ warn("make_delete_cookie_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
292
380
 
293
- key = escape(key)
294
- domain = value[:domain]
295
- path = value[:path]
296
- regexp = if domain
297
- if path
298
- /\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
299
- else
300
- /\A#{key}=.*domain=#{domain}(?:;|$)/
301
- end
302
- elsif path
303
- /\A#{key}=.*path=#{path}(?:;|$)/
304
- else
305
- /\A#{key}=/
306
- end
307
-
308
- cookies.reject! { |cookie| regexp.match? cookie }
309
-
310
- cookies.join("\n")
381
+ delete_set_cookie_header!(header, key, value)
311
382
  end
312
383
 
313
- def delete_cookie_header!(header, key, value = {})
314
- header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
315
- 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
316
388
  end
317
389
 
318
- # Adds a cookie that will *remove* a cookie from the client. Hence the
319
- # strange method name.
320
390
  def add_remove_cookie_to_header(header, key, value = {})
321
- 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)
322
392
 
323
- add_cookie_to_header(new_header, key,
324
- { value: '', path: nil, domain: nil,
325
- max_age: '0',
326
- expires: Time.at(0) }.merge(value))
393
+ delete_set_cookie_header!(header, key, value)
394
+ end
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
327
421
 
422
+ return header
328
423
  end
329
424
 
330
425
  def rfc2822(time)
331
426
  time.rfc2822
332
427
  end
333
428
 
334
- # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
335
- # of '% %b %Y'.
336
- # It assumes that the time is in GMT to comply to the RFC 2109.
337
- #
338
- # NOTE: I'm not sure the RFC says it requires GMT, but is ambiguous enough
339
- # that I'm certain someone implemented only that option.
340
- # Do not use %a and %b from Time.strptime, it would use localized names for
341
- # weekday and month.
342
- #
343
- def rfc2109(time)
344
- wday = RFC2822_DAY_NAME[time.wday]
345
- mon = RFC2822_MONTH_NAME[time.mon - 1]
346
- time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
347
- end
348
-
349
429
  # Parses the "Range:" header, if present, into an array of Range objects.
350
430
  # Returns nil if the header is missing or syntactically invalid.
351
431
  # Returns an empty array if none of the ranges are satisfiable.
352
432
  def byte_ranges(env, size)
353
- warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
354
433
  get_byte_ranges env['HTTP_RANGE'], size
355
434
  end
356
435
 
@@ -380,23 +459,36 @@ module Rack
380
459
  end
381
460
  ranges << (r0..r1) if r0 <= r1
382
461
  end
462
+
463
+ return [] if ranges.map(&:size).sum > size
464
+
383
465
  ranges
384
466
  end
385
467
 
386
- # Constant time string comparison.
387
- #
388
- # NOTE: the values compared should be of fixed length, such as strings
389
- # that have already been processed by HMAC. This should not be used
390
- # on variable length plaintext strings because it could leak length info
391
- # via timing attacks.
392
- def secure_compare(a, b)
393
- 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
394
485
 
395
- l = a.unpack("C*")
486
+ l = a.unpack("C*")
396
487
 
397
- r, i = 0, -1
398
- b.each_byte { |v| r |= v ^ l[i += 1] }
399
- r == 0
488
+ r, i = 0, -1
489
+ b.each_byte { |v| r |= v ^ l[i += 1] }
490
+ r == 0
491
+ end
400
492
  end
401
493
 
402
494
  # Context allows the use of a compatible middleware at different points
@@ -425,94 +517,32 @@ module Rack
425
517
  end
426
518
  end
427
519
 
428
- # A case-insensitive Hash that preserves the original case of a
520
+ # A wrapper around Headers
429
521
  # header when set.
430
522
  #
431
523
  # @api private
432
524
  class HeaderHash < Hash # :nodoc:
433
525
  def self.[](headers)
434
- 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?
435
528
  return headers
436
- else
437
- return self.new(headers)
438
529
  end
439
- end
440
530
 
441
- def initialize(hash = {})
442
- super()
443
- @names = {}
444
- hash.each { |k, v| self[k] = v }
531
+ new_headers = Headers.new
532
+ headers.each{|k,v| new_headers[k] = v}
533
+ new_headers
445
534
  end
446
535
 
447
- # on dup/clone, we need to duplicate @names hash
448
- def initialize_copy(other)
449
- super
450
- @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
451
541
  end
452
542
 
453
- # on clear, we need to clear @names hash
454
- def clear
455
- super
456
- @names.clear
543
+ def self.allocate
544
+ raise TypeError, "cannot allocate HeaderHash"
457
545
  end
458
-
459
- def each
460
- super do |k, v|
461
- yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
462
- end
463
- end
464
-
465
- def to_hash
466
- hash = {}
467
- each { |k, v| hash[k] = v }
468
- hash
469
- end
470
-
471
- def [](k)
472
- super(k) || super(@names[k.downcase])
473
- end
474
-
475
- def []=(k, v)
476
- canonical = k.downcase.freeze
477
- delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
478
- @names[canonical] = k
479
- super k, v
480
- end
481
-
482
- def delete(k)
483
- canonical = k.downcase
484
- result = super @names.delete(canonical)
485
- result
486
- end
487
-
488
- def include?(k)
489
- super || @names.include?(k.downcase)
490
- end
491
-
492
- alias_method :has_key?, :include?
493
- alias_method :member?, :include?
494
- alias_method :key?, :include?
495
-
496
- def merge!(other)
497
- other.each { |k, v| self[k] = v }
498
- self
499
- end
500
-
501
- def merge(other)
502
- hash = dup
503
- hash.merge! other
504
- end
505
-
506
- def replace(other)
507
- clear
508
- other.each { |k, v| self[k] = v }
509
- self
510
- end
511
-
512
- protected
513
- def names
514
- @names
515
- end
516
546
  end
517
547
 
518
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.8"
28
+ RELEASE = "3.0.9.1"
24
29
 
25
30
  # Return the Rack release as a dotted string.
26
31
  def self.release