rack 2.2.23 → 3.0.0

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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +148 -183
  3. data/CONTRIBUTING.md +53 -47
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +293 -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 -1
  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 +3 -1
  16. data/lib/rack/builder.rb +60 -42
  17. data/lib/rack/cascade.rb +2 -0
  18. data/lib/rack/chunked.rb +16 -13
  19. data/lib/rack/common_logger.rb +24 -20
  20. data/lib/rack/conditional_get.rb +18 -15
  21. data/lib/rack/constants.rb +63 -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 +12 -9
  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 +16 -18
  30. data/lib/rack/head.rb +9 -8
  31. data/lib/rack/headers.rb +154 -0
  32. data/lib/rack/lint.rb +754 -648
  33. data/lib/rack/lock.rb +2 -5
  34. data/lib/rack/logger.rb +2 -0
  35. data/lib/rack/media_type.rb +7 -17
  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 +126 -0
  41. data/lib/rack/multipart/generator.rb +7 -5
  42. data/lib/rack/multipart/parser.rb +119 -160
  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 -91
  47. data/lib/rack/recursive.rb +2 -0
  48. data/lib/rack/reloader.rb +0 -2
  49. data/lib/rack/request.rb +190 -95
  50. data/lib/rack/response.rb +131 -61
  51. data/lib/rack/rewindable_input.rb +24 -5
  52. data/lib/rack/runtime.rb +7 -6
  53. data/lib/rack/sendfile.rb +40 -65
  54. data/lib/rack/show_exceptions.rb +15 -2
  55. data/lib/rack/show_status.rb +17 -7
  56. data/lib/rack/static.rb +12 -17
  57. data/lib/rack/tempfile_reaper.rb +15 -4
  58. data/lib/rack/urlmap.rb +4 -2
  59. data/lib/rack/utils.rb +219 -240
  60. data/lib/rack/version.rb +9 -4
  61. data/lib/rack.rb +5 -76
  62. metadata +18 -35
  63. data/README.rdoc +0 -355
  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 -34
  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/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
@@ -59,24 +58,13 @@ module Rack
59
58
  end
60
59
 
61
60
  class << self
62
- attr_accessor :multipart_total_part_limit
63
-
64
- attr_accessor :multipart_file_limit
65
-
66
- # multipart_part_limit is the original name of multipart_file_limit, but
67
- # the limit only counts parts with filenames.
68
- alias multipart_part_limit multipart_file_limit
69
- alias multipart_part_limit= multipart_file_limit=
61
+ attr_accessor :multipart_part_limit
70
62
  end
71
63
 
72
- # The maximum number of file parts a request can contain. Accepting too
73
- # many parts can lead to the server running out of file handles.
64
+ # The maximum number of parts a request can contain. Accepting too many part
65
+ # can lead to the server running out of file handles.
74
66
  # Set to `0` for no limit.
75
- self.multipart_file_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_FILE_LIMIT'] || 128).to_i
76
-
77
- # The maximum total number of parts a request can contain. Accepting too
78
- # many can lead to excessive memory use and parsing time.
79
- self.multipart_total_part_limit = (ENV['RACK_MULTIPART_TOTAL_PART_LIMIT'] || 4096).to_i
67
+ self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i
80
68
 
81
69
  def self.param_depth_limit
82
70
  default_query_parser.param_depth_limit
@@ -87,11 +75,12 @@ module Rack
87
75
  end
88
76
 
89
77
  def self.key_space_limit
90
- 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
91
80
  end
92
81
 
93
82
  def self.key_space_limit=(v)
94
- 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)
95
84
  end
96
85
 
97
86
  if defined?(Process::CLOCK_MONOTONIC)
@@ -143,8 +132,8 @@ module Rack
143
132
  end
144
133
 
145
134
  def q_values(q_value_header)
146
- q_value_header.to_s.split(',').map do |part|
147
- value, parameters = part.split(';', 2).map(&:strip)
135
+ q_value_header.to_s.split(/\s*,\s*/).map do |part|
136
+ value, parameters = part.split(/\s*;\s*/, 2)
148
137
  quality = 1.0
149
138
  if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
150
139
  quality = md[1].to_f
@@ -153,6 +142,19 @@ module Rack
153
142
  end
154
143
  end
155
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
+
156
158
  # Return best accept value to use, based on the algorithm
157
159
  # in RFC 2616 Section 14. If there are multiple best
158
160
  # matches (same specificity and quality), the value returned
@@ -167,7 +169,7 @@ module Rack
167
169
  end.compact.sort_by do |match, quality|
168
170
  (match.split('/', 2).count('*') * -10) + quality
169
171
  end.last
170
- matches && matches.first
172
+ matches&.first
171
173
  end
172
174
 
173
175
  ESCAPE_HTML = {
@@ -186,41 +188,17 @@ module Rack
186
188
  string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
187
189
  end
188
190
 
189
- # Given an array of available encoding strings, and an array of
190
- # acceptable encodings for a request, where each element of the
191
- # acceptable encodings array is an array where the first element
192
- # is an encoding name and the second element is the numeric
193
- # priority for the encoding, return the available encoding with
194
- # the highest priority.
195
- #
196
- # The accept_encoding argument is typically generated by calling
197
- # Request#accept_encoding.
198
- #
199
- # Example:
200
- #
201
- # select_best_encoding(%w(compress gzip identity),
202
- # [["compress", 0.5], ["gzip", 1.0]])
203
- # # => "gzip"
204
- #
205
- # To reduce denial of service potential, only the first 16
206
- # acceptable encodings are considered.
207
191
  def select_best_encoding(available_encodings, accept_encoding)
208
192
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
209
193
 
210
- # Only process the first 16 encodings
211
- accept_encoding = accept_encoding[0...16]
212
194
  expanded_accept_encoding = []
213
- wildcard_seen = false
214
195
 
215
196
  accept_encoding.each do |m, q|
216
197
  preference = available_encodings.index(m) || available_encodings.size
217
198
 
218
199
  if m == "*"
219
- unless wildcard_seen
220
- (available_encodings - accept_encoding.map(&:first)).each do |m2|
221
- expanded_accept_encoding << [m2, q, preference]
222
- end
223
- wildcard_seen = true
200
+ (available_encodings - accept_encoding.map(&:first)).each do |m2|
201
+ expanded_accept_encoding << [m2, q, preference]
224
202
  end
225
203
  else
226
204
  expanded_accept_encoding << [m, q, preference]
@@ -228,13 +206,7 @@ module Rack
228
206
  end
229
207
 
230
208
  encoding_candidates = expanded_accept_encoding
231
- .sort do |(_, q1, p1), (_, q2, p2)|
232
- if r = (q1 <=> q2).nonzero?
233
- -r
234
- else
235
- (p1 <=> p2).nonzero? || 0
236
- end
237
- end
209
+ .sort_by { |_, q, p| [-q, p] }
238
210
  .map!(&:first)
239
211
 
240
212
  unless encoding_candidates.include?("identity")
@@ -248,17 +220,20 @@ module Rack
248
220
  (encoding_candidates & available_encodings)[0]
249
221
  end
250
222
 
251
- def parse_cookies(env)
252
- parse_cookies_header env[HTTP_COOKIE]
253
- 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
254
235
 
255
- def parse_cookies_header(header)
256
- # According to RFC 6265:
257
- # The syntax for cookie headers only supports semicolons
258
- # User Agent -> Server ==
259
- # Cookie: SID=31d4d96e407aad42; lang=en-US
260
- return {} unless header
261
- header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
236
+ value.split(/; */n).each_with_object({}) do |cookie, cookies|
262
237
  next if cookie.empty?
263
238
  key, value = cookie.split('=', 2)
264
239
  cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
@@ -266,14 +241,66 @@ module Rack
266
241
  end
267
242
 
268
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)
269
295
  case value
270
296
  when Hash
297
+ key = escape(key) unless value[:escape_key] == false
271
298
  domain = "; domain=#{value[:domain]}" if value[:domain]
272
299
  path = "; path=#{value[:path]}" if value[:path]
273
300
  max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
274
301
  expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
275
302
  secure = "; secure" if value[:secure]
276
- httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
303
+ httponly = "; httponly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
277
304
  same_site =
278
305
  case value[:same_site]
279
306
  when false, nil
@@ -288,121 +315,128 @@ module Rack
288
315
  raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
289
316
  end
290
317
  value = value[:value]
318
+ else
319
+ key = escape(key)
291
320
  end
321
+
292
322
  value = [value] unless Array === value
293
323
 
294
- cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \
324
+ return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
295
325
  "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
326
+ end
296
327
 
297
- case header
298
- when nil, ''
299
- cookie
300
- when String
301
- [header, cookie].join("\n")
302
- when Array
303
- (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
304
343
  else
305
- raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
344
+ headers[SET_COOKIE] = set_cookie_header(key, value)
306
345
  end
307
346
  end
308
347
 
309
- def set_cookie_header!(header, key, value)
310
- header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
311
- 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: ''))
312
364
  end
313
365
 
314
366
  def make_delete_cookie_header(header, key, value)
315
- case header
316
- when nil, ''
317
- cookies = []
318
- when String
319
- cookies = header.split("\n")
320
- when Array
321
- cookies = header
322
- end
323
-
324
- key = escape(key)
325
- domain = value[:domain]
326
- path = value[:path]
327
- regexp = if domain
328
- if path
329
- /\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
330
- else
331
- /\A#{key}=.*domain=#{domain}(?:;|$)/
332
- end
333
- elsif path
334
- /\A#{key}=.*path=#{path}(?:;|$)/
335
- else
336
- /\A#{key}=/
337
- end
338
-
339
- 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)
340
368
 
341
- cookies.join("\n")
369
+ delete_set_cookie_header!(header, key, value)
342
370
  end
343
371
 
344
- def delete_cookie_header!(header, key, value = {})
345
- header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
346
- 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
347
376
  end
348
377
 
349
- # Adds a cookie that will *remove* a cookie from the client. Hence the
350
- # strange method name.
351
378
  def add_remove_cookie_to_header(header, key, value = {})
352
- 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)
380
+
381
+ delete_set_cookie_header!(header, key, value)
382
+ end
353
383
 
354
- add_cookie_to_header(new_header, key,
355
- { value: '', path: nil, domain: nil,
356
- max_age: '0',
357
- expires: Time.at(0) }.merge(value))
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
358
409
 
410
+ return header
359
411
  end
360
412
 
361
413
  def rfc2822(time)
362
414
  time.rfc2822
363
415
  end
364
416
 
365
- # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
366
- # of '% %b %Y'.
367
- # It assumes that the time is in GMT to comply to the RFC 2109.
368
- #
369
- # NOTE: I'm not sure the RFC says it requires GMT, but is ambiguous enough
370
- # that I'm certain someone implemented only that option.
371
- # Do not use %a and %b from Time.strptime, it would use localized names for
372
- # weekday and month.
373
- #
374
- def rfc2109(time)
375
- wday = RFC2822_DAY_NAME[time.wday]
376
- mon = RFC2822_MONTH_NAME[time.mon - 1]
377
- time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
378
- end
379
-
380
417
  # Parses the "Range:" header, if present, into an array of Range objects.
381
418
  # Returns nil if the header is missing or syntactically invalid.
382
419
  # Returns an empty array if none of the ranges are satisfiable.
383
- def byte_ranges(env, size, max_ranges: 100)
384
- get_byte_ranges env['HTTP_RANGE'], size, max_ranges: max_ranges
420
+ def byte_ranges(env, size)
421
+ get_byte_ranges env['HTTP_RANGE'], size
385
422
  end
386
423
 
387
- def get_byte_ranges(http_range, size, max_ranges: 100)
424
+ def get_byte_ranges(http_range, size)
388
425
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
389
426
  return nil unless http_range && http_range =~ /bytes=([^;]+)/
390
- byte_range = $1
391
- return nil if byte_range.count(',') >= max_ranges
392
427
  ranges = []
393
- byte_range.split(/,[ \t]*/).each do |range_spec|
394
- return nil unless range_spec.include?('-')
395
- range = range_spec.split('-')
396
- r0, r1 = range[0], range[1]
397
- if r0.nil? || r0.empty?
398
- return nil if r1.nil?
428
+ $1.split(/,\s*/).each do |range_spec|
429
+ return nil unless range_spec =~ /(\d*)-(\d*)/
430
+ r0, r1 = $1, $2
431
+ if r0.empty?
432
+ return nil if r1.empty?
399
433
  # suffix-byte-range-spec, represents trailing suffix of file
400
434
  r0 = size - r1.to_i
401
435
  r0 = 0 if r0 < 0
402
436
  r1 = size - 1
403
437
  else
404
438
  r0 = r0.to_i
405
- if r1.nil?
439
+ if r1.empty?
406
440
  r1 = size - 1
407
441
  else
408
442
  r1 = r1.to_i
@@ -412,26 +446,33 @@ module Rack
412
446
  end
413
447
  ranges << (r0..r1) if r0 <= r1
414
448
  end
415
-
416
- return [] if ranges.map(&:size).inject(0, :+) > size
417
-
418
449
  ranges
419
450
  end
420
451
 
421
- # Constant time string comparison.
422
- #
423
- # NOTE: the values compared should be of fixed length, such as strings
424
- # that have already been processed by HMAC. This should not be used
425
- # on variable length plaintext strings because it could leak length info
426
- # via timing attacks.
427
- def secure_compare(a, b)
428
- 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
429
462
 
430
- l = a.unpack("C*")
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
431
469
 
432
- r, i = 0, -1
433
- b.each_byte { |v| r |= v ^ l[i += 1] }
434
- r == 0
470
+ l = a.unpack("C*")
471
+
472
+ r, i = 0, -1
473
+ b.each_byte { |v| r |= v ^ l[i += 1] }
474
+ r == 0
475
+ end
435
476
  end
436
477
 
437
478
  # Context allows the use of a compatible middleware at different points
@@ -460,94 +501,32 @@ module Rack
460
501
  end
461
502
  end
462
503
 
463
- # A case-insensitive Hash that preserves the original case of a
504
+ # A wrapper around Headers
464
505
  # header when set.
465
506
  #
466
507
  # @api private
467
508
  class HeaderHash < Hash # :nodoc:
468
509
  def self.[](headers)
469
- 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?
470
512
  return headers
471
- else
472
- return self.new(headers)
473
- end
474
- end
475
-
476
- def initialize(hash = {})
477
- super()
478
- @names = {}
479
- hash.each { |k, v| self[k] = v }
480
- end
481
-
482
- # on dup/clone, we need to duplicate @names hash
483
- def initialize_copy(other)
484
- super
485
- @names = other.names.dup
486
- end
487
-
488
- # on clear, we need to clear @names hash
489
- def clear
490
- super
491
- @names.clear
492
- end
493
-
494
- def each
495
- super do |k, v|
496
- yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
497
513
  end
498
- end
499
-
500
- def to_hash
501
- hash = {}
502
- each { |k, v| hash[k] = v }
503
- hash
504
- end
505
-
506
- def [](k)
507
- super(k) || super(@names[k.downcase])
508
- end
509
-
510
- def []=(k, v)
511
- canonical = k.downcase.freeze
512
- delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
513
- @names[canonical] = k
514
- super k, v
515
- end
516
514
 
517
- def delete(k)
518
- canonical = k.downcase
519
- result = super @names.delete(canonical)
520
- result
515
+ new_headers = Headers.new
516
+ headers.each{|k,v| new_headers[k] = v}
517
+ new_headers
521
518
  end
522
519
 
523
- def include?(k)
524
- super || @names.include?(k.downcase)
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
525
525
  end
526
526
 
527
- alias_method :has_key?, :include?
528
- alias_method :member?, :include?
529
- alias_method :key?, :include?
530
-
531
- def merge!(other)
532
- other.each { |k, v| self[k] = v }
533
- self
534
- end
535
-
536
- def merge(other)
537
- hash = dup
538
- hash.merge! other
527
+ def self.allocate
528
+ raise TypeError, "cannot allocate HeaderHash"
539
529
  end
540
-
541
- def replace(other)
542
- clear
543
- other.each { |k, v| self[k] = v }
544
- self
545
- end
546
-
547
- protected
548
- def names
549
- @names
550
- end
551
530
  end
552
531
 
553
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.23"
28
+ RELEASE = "3.0.0"
24
29
 
25
30
  # Return the Rack release as a dotted string.
26
31
  def self.release