rack 3.0.0 → 3.1.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.
data/lib/rack/response.rb CHANGED
@@ -25,25 +25,17 @@ module Rack
25
25
  self.new(body, status, headers)
26
26
  end
27
27
 
28
- CHUNKED = 'chunked'
29
28
  STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY
30
29
 
31
30
  attr_accessor :length, :status, :body
32
31
  attr_reader :headers
33
32
 
34
- # Deprecated, use headers instead.
35
- def header
36
- warn 'Rack::Response#header is deprecated and will be removed in Rack 3.1', uplevel: 1
37
-
38
- headers
39
- end
40
-
41
33
  # Initialize the response object with the specified +body+, +status+
42
34
  # and +headers+.
43
35
  #
44
36
  # If the +body+ is +nil+, construct an empty response object with internal
45
37
  # buffering.
46
- #
38
+ #
47
39
  # If the +body+ responds to +to_str+, assume it's a string-like object and
48
40
  # construct a buffered response object containing using that string as the
49
41
  # initial contents of the buffer.
@@ -62,7 +54,7 @@ module Rack
62
54
  @status = status.to_i
63
55
 
64
56
  unless headers.is_a?(Hash)
65
- warn "Providing non-hash headers to Rack::Response is deprecated and will be removed in Rack 3.1", uplevel: 1
57
+ raise ArgumentError, "Headers must be a Hash!"
66
58
  end
67
59
 
68
60
  @headers = Headers.new
@@ -97,21 +89,26 @@ module Rack
97
89
  self.status = status
98
90
  self.location = target
99
91
  end
100
-
101
- def chunked?
102
- CHUNKED == get_header(TRANSFER_ENCODING)
92
+
93
+ def no_entity_body?
94
+ # The response body is an enumerable body and it is not allowed to have an entity body.
95
+ @body.respond_to?(:each) && STATUS_WITH_NO_ENTITY_BODY[@status]
103
96
  end
104
97
 
105
98
  # Generate a response array consistent with the requirements of the SPEC.
106
99
  # @return [Array] a 3-tuple suitable of `[status, headers, body]`
107
100
  # which is suitable to be returned from the middleware `#call(env)` method.
108
101
  def finish(&block)
109
- if STATUS_WITH_NO_ENTITY_BODY[@status]
102
+ if no_entity_body?
110
103
  delete_header CONTENT_TYPE
111
104
  delete_header CONTENT_LENGTH
112
105
  close
113
106
  return [@status, @headers, []]
114
107
  else
108
+ if @length && @length > 0
109
+ set_header CONTENT_LENGTH, @length.to_s
110
+ end
111
+
115
112
  if block_given?
116
113
  @block = block
117
114
  return [@status, @headers, self]
@@ -315,27 +312,37 @@ module Rack
315
312
 
316
313
  protected
317
314
 
315
+ # Convert the body of this response into an internally buffered Array if possible.
316
+ #
317
+ # `@buffered` is a ternary value which indicates whether the body is buffered. It can be:
318
+ # * `nil` - The body has not been buffered yet.
319
+ # * `true` - The body is buffered as an Array instance.
320
+ # * `false` - The body is not buffered and cannot be buffered.
321
+ #
322
+ # @return [Boolean] whether the body is buffered as an Array instance.
318
323
  def buffered_body!
319
324
  if @buffered.nil?
320
325
  if @body.is_a?(Array)
321
326
  # The user supplied body was an array:
322
327
  @body = @body.compact
323
- @body.each do |part|
324
- @length += part.to_s.bytesize
325
- end
328
+ @length = @body.sum{|part| part.bytesize}
329
+ @buffered = true
326
330
  elsif @body.respond_to?(:each)
327
331
  # Turn the user supplied body into a buffered array:
328
332
  body = @body
329
333
  @body = Array.new
334
+ @length = 0
330
335
 
331
336
  body.each do |part|
332
337
  @writer.call(part.to_s)
333
338
  end
334
339
 
335
340
  body.close if body.respond_to?(:close)
336
-
341
+
342
+ # We have converted the body into an Array:
337
343
  @buffered = true
338
344
  else
345
+ # We don't know how to buffer the user-supplied body:
339
346
  @buffered = false
340
347
  end
341
348
  end
@@ -344,12 +351,10 @@ module Rack
344
351
  end
345
352
 
346
353
  def append(chunk)
354
+ chunk = chunk.dup unless chunk.frozen?
347
355
  @body << chunk
348
356
 
349
- unless chunked?
350
- @length += chunk.bytesize
351
- set_header(CONTENT_LENGTH, @length.to_s)
352
- end
357
+ @length += chunk.bytesize
353
358
 
354
359
  return chunk
355
360
  end
data/lib/rack/sendfile.rb CHANGED
@@ -111,7 +111,7 @@ module Rack
111
111
  end
112
112
 
113
113
  def call(env)
114
- status, headers, body = response = @app.call(env)
114
+ _, headers, body = response = @app.call(env)
115
115
 
116
116
  if body.respond_to?(:to_path)
117
117
  case type = variation(env)
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ostruct'
4
3
  require 'erb'
5
4
 
6
5
  require_relative 'constants'
@@ -19,6 +18,11 @@ module Rack
19
18
  class ShowExceptions
20
19
  CONTEXT = 7
21
20
 
21
+ Frame = Struct.new(:filename, :lineno, :function,
22
+ :pre_context_lineno, :pre_context,
23
+ :context_line, :post_context_lineno,
24
+ :post_context)
25
+
22
26
  def initialize(app)
23
27
  @app = app
24
28
  end
@@ -79,7 +83,7 @@ module Rack
79
83
  # This double assignment is to prevent an "unused variable" warning.
80
84
  # Yes, it is dumb, but I don't like Ruby yelling at me.
81
85
  frames = frames = exception.backtrace.map { |line|
82
- frame = OpenStruct.new
86
+ frame = Frame.new
83
87
  if line =~ /(.*?):(\d+)(:in `(.*)')?/
84
88
  frame.filename = $1
85
89
  frame.lineno = $2.to_i
data/lib/rack/urlmap.rb CHANGED
@@ -37,7 +37,7 @@ module Rack
37
37
  end
38
38
 
39
39
  location = location.chomp('/')
40
- match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
40
+ match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", Regexp::NOENCODING)
41
41
 
42
42
  [host, location, match, app]
43
43
  }.sort_by do |(host, location, _, _)|
data/lib/rack/utils.rb CHANGED
@@ -6,6 +6,7 @@ require 'fileutils'
6
6
  require 'set'
7
7
  require 'tempfile'
8
8
  require 'time'
9
+ require 'cgi/escape'
9
10
 
10
11
  require_relative 'query_parser'
11
12
  require_relative 'mime'
@@ -58,13 +59,24 @@ module Rack
58
59
  end
59
60
 
60
61
  class << self
61
- attr_accessor :multipart_part_limit
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=
62
70
  end
63
71
 
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.
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.
66
74
  # Set to `0` for no limit.
67
- self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i
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
68
80
 
69
81
  def self.param_depth_limit
70
82
  default_query_parser.param_depth_limit
@@ -74,15 +86,6 @@ module Rack
74
86
  self.default_query_parser = self.default_query_parser.new_depth_limit(v)
75
87
  end
76
88
 
77
- def self.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
80
- end
81
-
82
- def self.key_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)
84
- end
85
-
86
89
  if defined?(Process::CLOCK_MONOTONIC)
87
90
  def clock_time
88
91
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
@@ -121,19 +124,19 @@ module Rack
121
124
  }.join("&")
122
125
  when Hash
123
126
  value.map { |k, v|
124
- build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
127
+ build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
125
128
  }.delete_if(&:empty?).join('&')
126
129
  when nil
127
- prefix
130
+ escape(prefix)
128
131
  else
129
132
  raise ArgumentError, "value must be a Hash" if prefix.nil?
130
- "#{prefix}=#{escape(value)}"
133
+ "#{escape(prefix)}=#{escape(value)}"
131
134
  end
132
135
  end
133
136
 
134
137
  def q_values(q_value_header)
135
- q_value_header.to_s.split(/\s*,\s*/).map do |part|
136
- value, parameters = part.split(/\s*;\s*/, 2)
138
+ q_value_header.to_s.split(',').map do |part|
139
+ value, parameters = part.split(';', 2).map(&:strip)
137
140
  quality = 1.0
138
141
  if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
139
142
  quality = md[1].to_f
@@ -146,9 +149,10 @@ module Rack
146
149
  return nil unless forwarded_header
147
150
  forwarded_header = forwarded_header.to_s.gsub("\n", ";")
148
151
 
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
+ forwarded_header.split(';').each_with_object({}) do |field, values|
153
+ field.split(',').each do |pair|
154
+ pair = pair.split('=').map(&:strip).join('=')
155
+ return nil unless pair =~ /\A(by|for|host|proto)="?([^"]+)"?\Z/i
152
156
  (values[$1.downcase.to_sym] ||= []) << $2
153
157
  end
154
158
  end
@@ -172,21 +176,8 @@ module Rack
172
176
  matches&.first
173
177
  end
174
178
 
175
- ESCAPE_HTML = {
176
- "&" => "&amp;",
177
- "<" => "&lt;",
178
- ">" => "&gt;",
179
- "'" => "&#x27;",
180
- '"' => "&quot;",
181
- "/" => "&#x2F;"
182
- }
183
-
184
- ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
185
-
186
179
  # Escape ampersands, brackets and quotes to their HTML/XML entities.
187
- def escape_html(string)
188
- string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
189
- end
180
+ define_method(:escape_html, CGI.method(:escapeHTML))
190
181
 
191
182
  def select_best_encoding(available_encodings, accept_encoding)
192
183
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
@@ -240,21 +231,6 @@ module Rack
240
231
  end
241
232
  end
242
233
 
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
234
  # :call-seq:
259
235
  # parse_cookies(env) -> hash
260
236
  #
@@ -268,6 +244,20 @@ module Rack
268
244
  parse_cookies_header env[HTTP_COOKIE]
269
245
  end
270
246
 
247
+ # A valid cookie key according to RFC2616.
248
+ # A <cookie-name> can be any US-ASCII characters, except control characters, spaces, or tabs. It also must not contain a separator character like the following: ( ) < > @ , ; : \ " / [ ] ? = { }.
249
+ VALID_COOKIE_KEY = /\A[!#$%&'*+\-\.\^_`|~0-9a-zA-Z]+\z/.freeze
250
+ private_constant :VALID_COOKIE_KEY
251
+
252
+ private def escape_cookie_key(key)
253
+ if key =~ VALID_COOKIE_KEY
254
+ key
255
+ else
256
+ warn "Cookie key #{key.inspect} is not valid according to RFC2616; it will be escaped. This behaviour is deprecated and will be removed in a future version of Rack.", uplevel: 2
257
+ escape(key)
258
+ end
259
+ end
260
+
271
261
  # :call-seq:
272
262
  # set_cookie_header(key, value) -> encoded string
273
263
  #
@@ -278,7 +268,7 @@ module Rack
278
268
  # If the cookie +value+ is an instance of +Hash+, it considers the following
279
269
  # cookie attribute keys: +domain+, +max_age+, +expires+ (must be instance
280
270
  # of +Time+), +secure+, +http_only+, +same_site+ and +value+. For more
281
- # details about the interpretation of these fields, consult
271
+ # details about the interpretation of these fields, consult
282
272
  # [RFC6265 Section 5.2](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2).
283
273
  #
284
274
  # An extra cookie attribute +escape_key+ can be provided to control whether
@@ -294,7 +284,7 @@ module Rack
294
284
  def set_cookie_header(key, value)
295
285
  case value
296
286
  when Hash
297
- key = escape(key) unless value[:escape_key] == false
287
+ key = escape_cookie_key(key) unless value[:escape_key] == false
298
288
  domain = "; domain=#{value[:domain]}" if value[:domain]
299
289
  path = "; path=#{value[:path]}" if value[:path]
300
290
  max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
@@ -306,23 +296,24 @@ module Rack
306
296
  when false, nil
307
297
  nil
308
298
  when :none, 'None', :None
309
- '; SameSite=None'
299
+ '; samesite=none'
310
300
  when :lax, 'Lax', :Lax
311
- '; SameSite=Lax'
301
+ '; samesite=lax'
312
302
  when true, :strict, 'Strict', :Strict
313
- '; SameSite=Strict'
303
+ '; samesite=strict'
314
304
  else
315
- raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
305
+ raise ArgumentError, "Invalid :same_site value: #{value[:same_site].inspect}"
316
306
  end
307
+ partitioned = "; partitioned" if value[:partitioned]
317
308
  value = value[:value]
318
309
  else
319
- key = escape(key)
310
+ key = escape_cookie_key(key)
320
311
  end
321
312
 
322
313
  value = [value] unless Array === value
323
314
 
324
315
  return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
325
- "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
316
+ "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}#{partitioned}"
326
317
  end
327
318
 
328
319
  # :call-seq:
@@ -363,24 +354,12 @@ module Rack
363
354
  set_cookie_header(key, value.merge(max_age: '0', expires: Time.at(0), value: ''))
364
355
  end
365
356
 
366
- def make_delete_cookie_header(header, key, value)
367
- warn("make_delete_cookie_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
368
-
369
- delete_set_cookie_header!(header, key, value)
370
- end
371
-
372
357
  def delete_cookie_header!(headers, key, value = {})
373
358
  headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value)
374
359
 
375
360
  return nil
376
361
  end
377
362
 
378
- def add_remove_cookie_to_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
383
-
384
363
  # :call-seq:
385
364
  # delete_set_cookie_header!(header, key, value = {}) -> header value
386
365
  #
@@ -423,20 +402,23 @@ module Rack
423
402
 
424
403
  def get_byte_ranges(http_range, size)
425
404
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
405
+ # Ignore Range when file size is 0 to avoid a 416 error.
406
+ return nil if size.zero?
426
407
  return nil unless http_range && http_range =~ /bytes=([^;]+)/
427
408
  ranges = []
428
409
  $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?
410
+ return nil unless range_spec.include?('-')
411
+ range = range_spec.split('-')
412
+ r0, r1 = range[0], range[1]
413
+ if r0.nil? || r0.empty?
414
+ return nil if r1.nil?
433
415
  # suffix-byte-range-spec, represents trailing suffix of file
434
416
  r0 = size - r1.to_i
435
417
  r0 = 0 if r0 < 0
436
418
  r1 = size - 1
437
419
  else
438
420
  r0 = r0.to_i
439
- if r1.empty?
421
+ if r1.nil?
440
422
  r1 = size - 1
441
423
  else
442
424
  r1 = r1.to_i
@@ -446,6 +428,9 @@ module Rack
446
428
  end
447
429
  ranges << (r0..r1) if r0 <= r1
448
430
  end
431
+
432
+ return [] if ranges.map(&:size).sum > size
433
+
449
434
  ranges
450
435
  end
451
436
 
@@ -501,39 +486,12 @@ module Rack
501
486
  end
502
487
  end
503
488
 
504
- # A wrapper around Headers
505
- # header when set.
506
- #
507
- # @api private
508
- class HeaderHash < Hash # :nodoc:
509
- def self.[](headers)
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?
512
- return headers
513
- end
514
-
515
- new_headers = Headers.new
516
- headers.each{|k,v| new_headers[k] = v}
517
- new_headers
518
- end
519
-
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
- end
526
-
527
- def self.allocate
528
- raise TypeError, "cannot allocate HeaderHash"
529
- end
530
- end
531
-
532
489
  # Every standard HTTP code mapped to the appropriate message.
533
490
  # Generated with:
534
- # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
535
- # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
536
- # puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
491
+ # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv \
492
+ # | ruby -rcsv -e "puts CSV.parse(STDIN, headers: true) \
493
+ # .reject {|v| v['Description'] == 'Unassigned' or v['Description'].include? '(' } \
494
+ # .map {|v| %Q/#{v['Value']} => '#{v['Description']}'/ }.join(','+?\n)"
537
495
  HTTP_STATUS_CODES = {
538
496
  100 => 'Continue',
539
497
  101 => 'Switching Protocols',
@@ -555,7 +513,6 @@ module Rack
555
513
  303 => 'See Other',
556
514
  304 => 'Not Modified',
557
515
  305 => 'Use Proxy',
558
- 306 => '(Unused)',
559
516
  307 => 'Temporary Redirect',
560
517
  308 => 'Permanent Redirect',
561
518
  400 => 'Bad Request',
@@ -571,13 +528,13 @@ module Rack
571
528
  410 => 'Gone',
572
529
  411 => 'Length Required',
573
530
  412 => 'Precondition Failed',
574
- 413 => 'Payload Too Large',
531
+ 413 => 'Content Too Large',
575
532
  414 => 'URI Too Long',
576
533
  415 => 'Unsupported Media Type',
577
534
  416 => 'Range Not Satisfiable',
578
535
  417 => 'Expectation Failed',
579
536
  421 => 'Misdirected Request',
580
- 422 => 'Unprocessable Entity',
537
+ 422 => 'Unprocessable Content',
581
538
  423 => 'Locked',
582
539
  424 => 'Failed Dependency',
583
540
  425 => 'Too Early',
@@ -585,7 +542,7 @@ module Rack
585
542
  428 => 'Precondition Required',
586
543
  429 => 'Too Many Requests',
587
544
  431 => 'Request Header Fields Too Large',
588
- 451 => 'Unavailable for Legal Reasons',
545
+ 451 => 'Unavailable For Legal Reasons',
589
546
  500 => 'Internal Server Error',
590
547
  501 => 'Not Implemented',
591
548
  502 => 'Bad Gateway',
@@ -595,8 +552,6 @@ module Rack
595
552
  506 => 'Variant Also Negotiates',
596
553
  507 => 'Insufficient Storage',
597
554
  508 => 'Loop Detected',
598
- 509 => 'Bandwidth Limit Exceeded',
599
- 510 => 'Not Extended',
600
555
  511 => 'Network Authentication Required'
601
556
  }
602
557
 
@@ -604,12 +559,34 @@ module Rack
604
559
  STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
605
560
 
606
561
  SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
607
- [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
562
+ [message.downcase.gsub(/\s|-/, '_').to_sym, code]
608
563
  }.flatten]
609
564
 
565
+ OBSOLETE_SYMBOLS_TO_STATUS_CODES = {
566
+ payload_too_large: 413,
567
+ unprocessable_entity: 422,
568
+ bandwidth_limit_exceeded: 509,
569
+ not_extended: 510
570
+ }.freeze
571
+ private_constant :OBSOLETE_SYMBOLS_TO_STATUS_CODES
572
+
573
+ OBSOLETE_SYMBOL_MAPPINGS = {
574
+ payload_too_large: :content_too_large,
575
+ unprocessable_entity: :unprocessable_content
576
+ }.freeze
577
+ private_constant :OBSOLETE_SYMBOL_MAPPINGS
578
+
610
579
  def status_code(status)
611
580
  if status.is_a?(Symbol)
612
- SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
581
+ SYMBOL_TO_STATUS_CODE.fetch(status) do
582
+ fallback_code = OBSOLETE_SYMBOLS_TO_STATUS_CODES.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
583
+ message = "Status code #{status.inspect} is deprecated and will be removed in a future version of Rack."
584
+ if canonical_symbol = OBSOLETE_SYMBOL_MAPPINGS[status]
585
+ message = "#{message} Please use #{canonical_symbol.inspect} instead."
586
+ end
587
+ warn message, uplevel: 1
588
+ fallback_code
589
+ end
613
590
  else
614
591
  status.to_i
615
592
  end
data/lib/rack/version.rb CHANGED
@@ -12,20 +12,7 @@
12
12
  # so it should be enough just to <tt>require 'rack'</tt> in your code.
13
13
 
14
14
  module Rack
15
- # The Rack protocol version number implemented.
16
- VERSION = [1, 3].freeze
17
- deprecate_constant :VERSION
18
-
19
- VERSION_STRING = "1.3".freeze
20
- deprecate_constant :VERSION_STRING
21
-
22
- # The Rack protocol version number implemented.
23
- def self.version
24
- warn "Rack.version is deprecated and will be removed in Rack 3.1!", uplevel: 1
25
- VERSION
26
- end
27
-
28
- RELEASE = "3.0.0"
15
+ RELEASE = "3.1.0"
29
16
 
30
17
  # Return the Rack release as a dotted string.
31
18
  def self.release
data/lib/rack.rb CHANGED
@@ -15,23 +15,21 @@ require_relative 'rack/version'
15
15
  require_relative 'rack/constants'
16
16
 
17
17
  module Rack
18
- autoload :Builder, "rack/builder"
18
+ autoload :BadRequest, "rack/bad_request"
19
19
  autoload :BodyProxy, "rack/body_proxy"
20
+ autoload :Builder, "rack/builder"
20
21
  autoload :Cascade, "rack/cascade"
21
- autoload :Chunked, "rack/chunked"
22
22
  autoload :CommonLogger, "rack/common_logger"
23
23
  autoload :ConditionalGet, "rack/conditional_get"
24
24
  autoload :Config, "rack/config"
25
25
  autoload :ContentLength, "rack/content_length"
26
26
  autoload :ContentType, "rack/content_type"
27
+ autoload :Deflater, "rack/deflater"
28
+ autoload :Directory, "rack/directory"
27
29
  autoload :ETag, "rack/etag"
28
30
  autoload :Events, "rack/events"
29
- autoload :File, "rack/file"
30
31
  autoload :Files, "rack/files"
31
- autoload :Deflater, "rack/deflater"
32
- autoload :Directory, "rack/directory"
33
32
  autoload :ForwardRequest, "rack/recursive"
34
- autoload :Handler, "rack/handler"
35
33
  autoload :Head, "rack/head"
36
34
  autoload :Headers, "rack/headers"
37
35
  autoload :Lint, "rack/lint"
@@ -40,31 +38,28 @@ module Rack
40
38
  autoload :MediaType, "rack/media_type"
41
39
  autoload :MethodOverride, "rack/method_override"
42
40
  autoload :Mime, "rack/mime"
41
+ autoload :MockRequest, "rack/mock_request"
42
+ autoload :MockResponse, "rack/mock_response"
43
+ autoload :Multipart, "rack/multipart"
43
44
  autoload :NullLogger, "rack/null_logger"
45
+ autoload :QueryParser, "rack/query_parser"
44
46
  autoload :Recursive, "rack/recursive"
45
47
  autoload :Reloader, "rack/reloader"
48
+ autoload :Request, "rack/request"
49
+ autoload :Response, "rack/response"
46
50
  autoload :RewindableInput, "rack/rewindable_input"
47
51
  autoload :Runtime, "rack/runtime"
48
52
  autoload :Sendfile, "rack/sendfile"
49
- autoload :Server, "rack/server"
50
53
  autoload :ShowExceptions, "rack/show_exceptions"
51
54
  autoload :ShowStatus, "rack/show_status"
52
55
  autoload :Static, "rack/static"
53
56
  autoload :TempfileReaper, "rack/tempfile_reaper"
54
57
  autoload :URLMap, "rack/urlmap"
55
58
  autoload :Utils, "rack/utils"
56
- autoload :Multipart, "rack/multipart"
57
-
58
- autoload :MockRequest, "rack/mock_request"
59
- autoload :MockResponse, "rack/mock_response"
60
-
61
- autoload :Request, "rack/request"
62
- autoload :Response, "rack/response"
63
59
 
64
60
  module Auth
65
61
  autoload :Basic, "rack/auth/basic"
66
- autoload :AbstractRequest, "rack/auth/abstract/request"
67
62
  autoload :AbstractHandler, "rack/auth/abstract/handler"
68
- autoload :Digest, "rack/auth/digest"
63
+ autoload :AbstractRequest, "rack/auth/abstract/request"
69
64
  end
70
65
  end