rack 2.2.3.1 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +174 -69
  3. data/CONTRIBUTING.md +53 -47
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +293 -0
  6. data/SPEC.rdoc +183 -131
  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/digest/md5.rb +1 -131
  10. data/lib/rack/auth/digest/nonce.rb +1 -54
  11. data/lib/rack/auth/digest/params.rb +1 -54
  12. data/lib/rack/auth/digest/request.rb +1 -43
  13. data/lib/rack/auth/digest.rb +256 -0
  14. data/lib/rack/body_proxy.rb +3 -1
  15. data/lib/rack/builder.rb +60 -42
  16. data/lib/rack/cascade.rb +2 -0
  17. data/lib/rack/chunked.rb +16 -13
  18. data/lib/rack/common_logger.rb +23 -18
  19. data/lib/rack/conditional_get.rb +18 -15
  20. data/lib/rack/constants.rb +63 -0
  21. data/lib/rack/content_length.rb +12 -16
  22. data/lib/rack/content_type.rb +8 -5
  23. data/lib/rack/deflater.rb +40 -26
  24. data/lib/rack/directory.rb +9 -3
  25. data/lib/rack/etag.rb +14 -21
  26. data/lib/rack/events.rb +4 -0
  27. data/lib/rack/file.rb +2 -0
  28. data/lib/rack/files.rb +15 -17
  29. data/lib/rack/head.rb +9 -8
  30. data/lib/rack/headers.rb +154 -0
  31. data/lib/rack/lint.rb +779 -684
  32. data/lib/rack/lock.rb +2 -5
  33. data/lib/rack/logger.rb +2 -0
  34. data/lib/rack/media_type.rb +1 -1
  35. data/lib/rack/method_override.rb +4 -0
  36. data/lib/rack/mime.rb +8 -0
  37. data/lib/rack/mock.rb +1 -271
  38. data/lib/rack/mock_request.rb +166 -0
  39. data/lib/rack/mock_response.rb +126 -0
  40. data/lib/rack/multipart/generator.rb +7 -5
  41. data/lib/rack/multipart/parser.rb +118 -61
  42. data/lib/rack/multipart/uploaded_file.rb +4 -0
  43. data/lib/rack/multipart.rb +20 -40
  44. data/lib/rack/null_logger.rb +9 -0
  45. data/lib/rack/query_parser.rb +80 -44
  46. data/lib/rack/recursive.rb +2 -0
  47. data/lib/rack/reloader.rb +0 -2
  48. data/lib/rack/request.rb +187 -89
  49. data/lib/rack/response.rb +131 -61
  50. data/lib/rack/rewindable_input.rb +24 -5
  51. data/lib/rack/runtime.rb +7 -6
  52. data/lib/rack/sendfile.rb +30 -25
  53. data/lib/rack/show_exceptions.rb +15 -2
  54. data/lib/rack/show_status.rb +17 -7
  55. data/lib/rack/static.rb +8 -8
  56. data/lib/rack/tempfile_reaper.rb +15 -4
  57. data/lib/rack/urlmap.rb +3 -1
  58. data/lib/rack/utils.rb +199 -170
  59. data/lib/rack/version.rb +9 -4
  60. data/lib/rack.rb +5 -76
  61. metadata +16 -36
  62. data/README.rdoc +0 -306
  63. data/Rakefile +0 -130
  64. data/bin/rackup +0 -5
  65. data/contrib/rack.png +0 -0
  66. data/contrib/rack.svg +0 -150
  67. data/contrib/rack_logo.svg +0 -164
  68. data/contrib/rdoc.css +0 -412
  69. data/example/lobster.ru +0 -6
  70. data/example/protectedlobster.rb +0 -16
  71. data/example/protectedlobster.ru +0 -10
  72. data/lib/rack/core_ext/regexp.rb +0 -14
  73. data/lib/rack/handler/cgi.rb +0 -59
  74. data/lib/rack/handler/fastcgi.rb +0 -100
  75. data/lib/rack/handler/lsws.rb +0 -61
  76. data/lib/rack/handler/scgi.rb +0 -71
  77. data/lib/rack/handler/thin.rb +0 -36
  78. data/lib/rack/handler/webrick.rb +0 -129
  79. data/lib/rack/handler.rb +0 -104
  80. data/lib/rack/lobster.rb +0 -70
  81. data/lib/rack/server.rb +0 -466
  82. data/lib/rack/session/abstract/id.rb +0 -523
  83. data/lib/rack/session/cookie.rb +0 -203
  84. data/lib/rack/session/memcache.rb +0 -10
  85. data/lib/rack/session/pool.rb +0 -85
  86. 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,16 +8,18 @@ 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
@@ -25,9 +27,10 @@ module Rack
25
27
  class << self
26
28
  attr_accessor :default_query_parser
27
29
  end
28
- # The default number of bytes to allow parameter keys to take up.
29
- # This helps prevent a rogue client from flooding a Request.
30
- 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)
31
34
 
32
35
  module_function
33
36
 
@@ -72,11 +75,12 @@ module Rack
72
75
  end
73
76
 
74
77
  def self.key_space_limit
75
- default_query_parser.key_space_limit
78
+ 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)
79
+ 65536
76
80
  end
77
81
 
78
82
  def self.key_space_limit=(v)
79
- self.default_query_parser = self.default_query_parser.new_space_limit(v)
83
+ warn("`Rack::Utils.key_space_limit=` is deprecated and no longer has an effect. It will be removed in Rack 3.1", uplevel: 1)
80
84
  end
81
85
 
82
86
  if defined?(Process::CLOCK_MONOTONIC)
@@ -138,6 +142,19 @@ module Rack
138
142
  end
139
143
  end
140
144
 
145
+ def forwarded_values(forwarded_header)
146
+ return nil unless forwarded_header
147
+ forwarded_header = forwarded_header.to_s.gsub("\n", ";")
148
+
149
+ forwarded_header.split(/\s*;\s*/).each_with_object({}) do |field, values|
150
+ field.split(/\s*,\s*/).each do |pair|
151
+ return nil unless pair =~ /\A\s*(by|for|host|proto)\s*=\s*"?([^"]+)"?\s*\Z/i
152
+ (values[$1.downcase.to_sym] ||= []) << $2
153
+ end
154
+ end
155
+ end
156
+ module_function :forwarded_values
157
+
141
158
  # Return best accept value to use, based on the algorithm
142
159
  # in RFC 2616 Section 14. If there are multiple best
143
160
  # matches (same specificity and quality), the value returned
@@ -152,7 +169,7 @@ module Rack
152
169
  end.compact.sort_by do |match, quality|
153
170
  (match.split('/', 2).count('*') * -10) + quality
154
171
  end.last
155
- matches && matches.first
172
+ matches&.first
156
173
  end
157
174
 
158
175
  ESCAPE_HTML = {
@@ -203,17 +220,20 @@ module Rack
203
220
  (encoding_candidates & available_encodings)[0]
204
221
  end
205
222
 
206
- def parse_cookies(env)
207
- parse_cookies_header env[HTTP_COOKIE]
208
- end
223
+ # :call-seq:
224
+ # parse_cookies_header(value) -> hash
225
+ #
226
+ # Parse cookies from the provided header +value+ according to RFC6265. The
227
+ # syntax for cookie headers only supports semicolons. Returns a map of
228
+ # cookie +key+ to cookie +value+.
229
+ #
230
+ # parse_cookies_header('myname=myvalue; max-age=0')
231
+ # # => {"myname"=>"myvalue", "max-age"=>"0"}
232
+ #
233
+ def parse_cookies_header(value)
234
+ return {} unless value
209
235
 
210
- def parse_cookies_header(header)
211
- # According to RFC 6265:
212
- # The syntax for cookie headers only supports semicolons
213
- # User Agent -> Server ==
214
- # Cookie: SID=31d4d96e407aad42; lang=en-US
215
- return {} unless header
216
- header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
236
+ value.split(/; */n).each_with_object({}) do |cookie, cookies|
217
237
  next if cookie.empty?
218
238
  key, value = cookie.split('=', 2)
219
239
  cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
@@ -221,14 +241,66 @@ module Rack
221
241
  end
222
242
 
223
243
  def add_cookie_to_header(header, key, value)
244
+ warn("add_cookie_to_header is deprecated and will be removed in Rack 3.1", uplevel: 1)
245
+
246
+ case header
247
+ when nil, ''
248
+ return set_cookie_header(key, value)
249
+ when String
250
+ [header, set_cookie_header(key, value)]
251
+ when Array
252
+ header + [set_cookie_header(key, value)]
253
+ else
254
+ raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
255
+ end
256
+ end
257
+
258
+ # :call-seq:
259
+ # parse_cookies(env) -> hash
260
+ #
261
+ # Parse cookies from the provided request environment using
262
+ # parse_cookies_header. Returns a map of cookie +key+ to cookie +value+.
263
+ #
264
+ # parse_cookies({'HTTP_COOKIE' => 'myname=myvalue'})
265
+ # # => {'myname' => 'myvalue'}
266
+ #
267
+ def parse_cookies(env)
268
+ parse_cookies_header env[HTTP_COOKIE]
269
+ end
270
+
271
+ # :call-seq:
272
+ # set_cookie_header(key, value) -> encoded string
273
+ #
274
+ # Generate an encoded string using the provided +key+ and +value+ suitable
275
+ # for the +set-cookie+ header according to RFC6265. The +value+ may be an
276
+ # instance of either +String+ or +Hash+.
277
+ #
278
+ # If the cookie +value+ is an instance of +Hash+, it considers the following
279
+ # cookie attribute keys: +domain+, +max_age+, +expires+ (must be instance
280
+ # of +Time+), +secure+, +http_only+, +same_site+ and +value+. For more
281
+ # details about the interpretation of these fields, consult
282
+ # [RFC6265 Section 5.2](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2).
283
+ #
284
+ # An extra cookie attribute +escape_key+ can be provided to control whether
285
+ # or not the cookie key is URL encoded. If explicitly set to +false+, the
286
+ # cookie key name will not be url encoded (escaped). The default is +true+.
287
+ #
288
+ # set_cookie_header("myname", "myvalue")
289
+ # # => "myname=myvalue"
290
+ #
291
+ # set_cookie_header("myname", {value: "myvalue", max_age: 10})
292
+ # # => "myname=myvalue; max-age=10"
293
+ #
294
+ def set_cookie_header(key, value)
224
295
  case value
225
296
  when Hash
297
+ key = escape(key) unless value[:escape_key] == false
226
298
  domain = "; domain=#{value[:domain]}" if value[:domain]
227
299
  path = "; path=#{value[:path]}" if value[:path]
228
300
  max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
229
301
  expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
230
302
  secure = "; secure" if value[:secure]
231
- httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
303
+ httponly = "; httponly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
232
304
  same_site =
233
305
  case value[:same_site]
234
306
  when false, nil
@@ -243,100 +315,109 @@ module Rack
243
315
  raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
244
316
  end
245
317
  value = value[:value]
318
+ else
319
+ key = escape(key)
246
320
  end
321
+
247
322
  value = [value] unless Array === value
248
323
 
249
- cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \
324
+ return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
250
325
  "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
326
+ end
251
327
 
252
- case header
253
- when nil, ''
254
- cookie
255
- when String
256
- [header, cookie].join("\n")
257
- when Array
258
- (header + [cookie]).join("\n")
328
+ # :call-seq:
329
+ # set_cookie_header!(headers, key, value) -> header value
330
+ #
331
+ # Append a cookie in the specified headers with the given cookie +key+ and
332
+ # +value+ using set_cookie_header.
333
+ #
334
+ # If the headers already contains a +set-cookie+ key, it will be converted
335
+ # to an +Array+ if not already, and appended to.
336
+ def set_cookie_header!(headers, key, value)
337
+ if header = headers[SET_COOKIE]
338
+ if header.is_a?(Array)
339
+ header << set_cookie_header(key, value)
340
+ else
341
+ headers[SET_COOKIE] = [header, set_cookie_header(key, value)]
342
+ end
259
343
  else
260
- raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
344
+ headers[SET_COOKIE] = set_cookie_header(key, value)
261
345
  end
262
346
  end
263
347
 
264
- def set_cookie_header!(header, key, value)
265
- header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
266
- nil
348
+ # :call-seq:
349
+ # delete_set_cookie_header(key, value = {}) -> encoded string
350
+ #
351
+ # Generate an encoded string based on the given +key+ and +value+ using
352
+ # set_cookie_header for the purpose of causing the specified cookie to be
353
+ # deleted. The +value+ may be an instance of +Hash+ and can include
354
+ # attributes as outlined by set_cookie_header. The encoded cookie will have
355
+ # a +max_age+ of 0 seconds, an +expires+ date in the past and an empty
356
+ # +value+. When used with the +set-cookie+ header, it will cause the client
357
+ # to *remove* any matching cookie.
358
+ #
359
+ # delete_set_cookie_header("myname")
360
+ # # => "myname=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
361
+ #
362
+ def delete_set_cookie_header(key, value = {})
363
+ set_cookie_header(key, value.merge(max_age: '0', expires: Time.at(0), value: ''))
267
364
  end
268
365
 
269
366
  def make_delete_cookie_header(header, key, value)
270
- case header
271
- when nil, ''
272
- cookies = []
273
- when String
274
- cookies = header.split("\n")
275
- when Array
276
- cookies = header
277
- end
278
-
279
- key = escape(key)
280
- domain = value[:domain]
281
- path = value[:path]
282
- regexp = if domain
283
- if path
284
- /\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
285
- else
286
- /\A#{key}=.*domain=#{domain}(?:;|$)/
287
- end
288
- elsif path
289
- /\A#{key}=.*path=#{path}(?:;|$)/
290
- else
291
- /\A#{key}=/
292
- end
293
-
294
- cookies.reject! { |cookie| regexp.match? cookie }
367
+ warn("make_delete_cookie_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
295
368
 
296
- cookies.join("\n")
369
+ delete_set_cookie_header!(header, key, value)
297
370
  end
298
371
 
299
- def delete_cookie_header!(header, key, value = {})
300
- header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
301
- nil
372
+ def delete_cookie_header!(headers, key, value = {})
373
+ headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value)
374
+
375
+ return nil
302
376
  end
303
377
 
304
- # Adds a cookie that will *remove* a cookie from the client. Hence the
305
- # strange method name.
306
378
  def add_remove_cookie_to_header(header, key, value = {})
307
- new_header = make_delete_cookie_header(header, key, value)
379
+ warn("add_remove_cookie_to_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
308
380
 
309
- add_cookie_to_header(new_header, key,
310
- { value: '', path: nil, domain: nil,
311
- max_age: '0',
312
- expires: Time.at(0) }.merge(value))
381
+ delete_set_cookie_header!(header, key, value)
382
+ end
313
383
 
384
+ # :call-seq:
385
+ # delete_set_cookie_header!(header, key, value = {}) -> header value
386
+ #
387
+ # Set an expired cookie in the specified headers with the given cookie
388
+ # +key+ and +value+ using delete_set_cookie_header. This causes
389
+ # the client to immediately delete the specified cookie.
390
+ #
391
+ # delete_set_cookie_header!(nil, "mycookie")
392
+ # # => "mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
393
+ #
394
+ # If the header is non-nil, it will be modified in place.
395
+ #
396
+ # header = []
397
+ # delete_set_cookie_header!(header, "mycookie")
398
+ # # => ["mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"]
399
+ # header
400
+ # # => ["mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"]
401
+ #
402
+ def delete_set_cookie_header!(header, key, value = {})
403
+ if header
404
+ header = Array(header)
405
+ header << delete_set_cookie_header(key, value)
406
+ else
407
+ header = delete_set_cookie_header(key, value)
408
+ end
409
+
410
+ return header
314
411
  end
315
412
 
316
413
  def rfc2822(time)
317
414
  time.rfc2822
318
415
  end
319
416
 
320
- # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
321
- # of '% %b %Y'.
322
- # It assumes that the time is in GMT to comply to the RFC 2109.
323
- #
324
- # NOTE: I'm not sure the RFC says it requires GMT, but is ambiguous enough
325
- # that I'm certain someone implemented only that option.
326
- # Do not use %a and %b from Time.strptime, it would use localized names for
327
- # weekday and month.
328
- #
329
- def rfc2109(time)
330
- wday = Time::RFC2822_DAY_NAME[time.wday]
331
- mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
332
- time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
333
- end
334
-
335
417
  # Parses the "Range:" header, if present, into an array of Range objects.
336
418
  # Returns nil if the header is missing or syntactically invalid.
337
419
  # Returns an empty array if none of the ranges are satisfiable.
338
420
  def byte_ranges(env, size)
339
- warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
340
421
  get_byte_ranges env['HTTP_RANGE'], size
341
422
  end
342
423
 
@@ -368,20 +449,30 @@ module Rack
368
449
  ranges
369
450
  end
370
451
 
371
- # Constant time string comparison.
372
- #
373
- # NOTE: the values compared should be of fixed length, such as strings
374
- # that have already been processed by HMAC. This should not be used
375
- # on variable length plaintext strings because it could leak length info
376
- # via timing attacks.
377
- def secure_compare(a, b)
378
- return false unless a.bytesize == b.bytesize
452
+ # :nocov:
453
+ if defined?(OpenSSL.fixed_length_secure_compare)
454
+ # Constant time string comparison.
455
+ #
456
+ # NOTE: the values compared should be of fixed length, such as strings
457
+ # that have already been processed by HMAC. This should not be used
458
+ # on variable length plaintext strings because it could leak length info
459
+ # via timing attacks.
460
+ def secure_compare(a, b)
461
+ return false unless a.bytesize == b.bytesize
462
+
463
+ OpenSSL.fixed_length_secure_compare(a, b)
464
+ end
465
+ # :nocov:
466
+ else
467
+ def secure_compare(a, b)
468
+ return false unless a.bytesize == b.bytesize
379
469
 
380
- l = a.unpack("C*")
470
+ l = a.unpack("C*")
381
471
 
382
- r, i = 0, -1
383
- b.each_byte { |v| r |= v ^ l[i += 1] }
384
- r == 0
472
+ r, i = 0, -1
473
+ b.each_byte { |v| r |= v ^ l[i += 1] }
474
+ r == 0
475
+ end
385
476
  end
386
477
 
387
478
  # Context allows the use of a compatible middleware at different points
@@ -410,94 +501,32 @@ module Rack
410
501
  end
411
502
  end
412
503
 
413
- # A case-insensitive Hash that preserves the original case of a
504
+ # A wrapper around Headers
414
505
  # header when set.
415
506
  #
416
507
  # @api private
417
508
  class HeaderHash < Hash # :nodoc:
418
509
  def self.[](headers)
419
- if headers.is_a?(HeaderHash) && !headers.frozen?
510
+ warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
511
+ if headers.is_a?(Headers) && !headers.frozen?
420
512
  return headers
421
- else
422
- return self.new(headers)
423
513
  end
424
- end
425
514
 
426
- def initialize(hash = {})
427
- super()
428
- @names = {}
429
- hash.each { |k, v| self[k] = v }
515
+ new_headers = Headers.new
516
+ headers.each{|k,v| new_headers[k] = v}
517
+ new_headers
430
518
  end
431
519
 
432
- # on dup/clone, we need to duplicate @names hash
433
- def initialize_copy(other)
434
- super
435
- @names = other.names.dup
520
+ def self.new(hash = {})
521
+ warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
522
+ headers = Headers.new
523
+ hash.each{|k,v| headers[k] = v}
524
+ headers
436
525
  end
437
526
 
438
- # on clear, we need to clear @names hash
439
- def clear
440
- super
441
- @names.clear
527
+ def self.allocate
528
+ raise TypeError, "cannot allocate HeaderHash"
442
529
  end
443
-
444
- def each
445
- super do |k, v|
446
- yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
447
- end
448
- end
449
-
450
- def to_hash
451
- hash = {}
452
- each { |k, v| hash[k] = v }
453
- hash
454
- end
455
-
456
- def [](k)
457
- super(k) || super(@names[k.downcase])
458
- end
459
-
460
- def []=(k, v)
461
- canonical = k.downcase.freeze
462
- delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
463
- @names[canonical] = k
464
- super k, v
465
- end
466
-
467
- def delete(k)
468
- canonical = k.downcase
469
- result = super @names.delete(canonical)
470
- result
471
- end
472
-
473
- def include?(k)
474
- super || @names.include?(k.downcase)
475
- end
476
-
477
- alias_method :has_key?, :include?
478
- alias_method :member?, :include?
479
- alias_method :key?, :include?
480
-
481
- def merge!(other)
482
- other.each { |k, v| self[k] = v }
483
- self
484
- end
485
-
486
- def merge(other)
487
- hash = dup
488
- hash.merge! other
489
- end
490
-
491
- def replace(other)
492
- clear
493
- other.each { |k, v| self[k] = v }
494
- self
495
- end
496
-
497
- protected
498
- def names
499
- @names
500
- end
501
530
  end
502
531
 
503
532
  # 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.3.1"
28
+ RELEASE = "3.0.0.rc1"
24
29
 
25
30
  # Return the Rack release as a dotted string.
26
31
  def self.release