rack 3.0.0 → 3.2.7

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +592 -1
  3. data/CONTRIBUTING.md +11 -9
  4. data/README.md +120 -29
  5. data/SPEC.rdoc +206 -288
  6. data/lib/rack/auth/abstract/request.rb +2 -0
  7. data/lib/rack/auth/basic.rb +1 -4
  8. data/lib/rack/bad_request.rb +8 -0
  9. data/lib/rack/body_proxy.rb +18 -2
  10. data/lib/rack/builder.rb +61 -40
  11. data/lib/rack/cascade.rb +0 -3
  12. data/lib/rack/common_logger.rb +3 -2
  13. data/lib/rack/conditional_get.rb +4 -3
  14. data/lib/rack/constants.rb +6 -1
  15. data/lib/rack/directory.rb +6 -3
  16. data/lib/rack/etag.rb +3 -0
  17. data/lib/rack/events.rb +21 -6
  18. data/lib/rack/files.rb +1 -1
  19. data/lib/rack/head.rb +2 -3
  20. data/lib/rack/headers.rb +98 -14
  21. data/lib/rack/lint.rb +491 -428
  22. data/lib/rack/media_type.rb +18 -9
  23. data/lib/rack/method_override.rb +2 -2
  24. data/lib/rack/mime.rb +6 -5
  25. data/lib/rack/mock_request.rb +10 -15
  26. data/lib/rack/mock_response.rb +50 -20
  27. data/lib/rack/multipart/parser.rb +279 -80
  28. data/lib/rack/multipart/uploaded_file.rb +42 -5
  29. data/lib/rack/multipart.rb +34 -1
  30. data/lib/rack/query_parser.rb +88 -80
  31. data/lib/rack/request.rb +98 -65
  32. data/lib/rack/response.rb +35 -20
  33. data/lib/rack/rewindable_input.rb +4 -1
  34. data/lib/rack/sendfile.rb +53 -23
  35. data/lib/rack/show_exceptions.rb +10 -4
  36. data/lib/rack/show_status.rb +0 -2
  37. data/lib/rack/static.rb +9 -4
  38. data/lib/rack/urlmap.rb +1 -1
  39. data/lib/rack/utils.rb +205 -133
  40. data/lib/rack/version.rb +3 -20
  41. data/lib/rack.rb +11 -17
  42. metadata +6 -15
  43. data/lib/rack/auth/digest/md5.rb +0 -1
  44. data/lib/rack/auth/digest/nonce.rb +0 -1
  45. data/lib/rack/auth/digest/params.rb +0 -1
  46. data/lib/rack/auth/digest/request.rb +0 -1
  47. data/lib/rack/auth/digest.rb +0 -256
  48. data/lib/rack/chunked.rb +0 -120
  49. data/lib/rack/file.rb +0 -9
  50. data/lib/rack/logger.rb +0 -22
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 'erb'
9
10
 
10
11
  require_relative 'query_parser'
11
12
  require_relative 'mime'
@@ -23,6 +24,7 @@ module Rack
23
24
  DEFAULT_SEP = QueryParser::DEFAULT_SEP
24
25
  COMMON_SEP = QueryParser::COMMON_SEP
25
26
  KeySpaceConstrainedParams = QueryParser::Params
27
+ URI_PARSER = defined?(::URI::RFC2396_PARSER) ? ::URI::RFC2396_PARSER : ::URI::DEFAULT_PARSER
26
28
 
27
29
  class << self
28
30
  attr_accessor :default_query_parser
@@ -42,13 +44,13 @@ module Rack
42
44
  # Like URI escaping, but with %20 instead of +. Strictly speaking this is
43
45
  # true URI escaping.
44
46
  def escape_path(s)
45
- ::URI::DEFAULT_PARSER.escape s
47
+ URI_PARSER.escape s
46
48
  end
47
49
 
48
50
  # Unescapes the **path** component of a URI. See Rack::Utils.unescape for
49
51
  # unescaping query parameters or form components.
50
52
  def unescape_path(s)
51
- ::URI::DEFAULT_PARSER.unescape s
53
+ URI_PARSER.unescape s
52
54
  end
53
55
 
54
56
  # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
@@ -58,13 +60,24 @@ module Rack
58
60
  end
59
61
 
60
62
  class << self
61
- attr_accessor :multipart_part_limit
63
+ attr_accessor :multipart_total_part_limit
64
+
65
+ attr_accessor :multipart_file_limit
66
+
67
+ # multipart_part_limit is the original name of multipart_file_limit, but
68
+ # the limit only counts parts with filenames.
69
+ alias multipart_part_limit multipart_file_limit
70
+ alias multipart_part_limit= multipart_file_limit=
62
71
  end
63
72
 
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.
73
+ # The maximum number of file parts a request can contain. Accepting too
74
+ # many parts can lead to the server running out of file handles.
66
75
  # Set to `0` for no limit.
67
- self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i
76
+ self.multipart_file_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_FILE_LIMIT'] || 128).to_i
77
+
78
+ # The maximum total number of parts a request can contain. Accepting too
79
+ # many can lead to excessive memory use and parsing time.
80
+ self.multipart_total_part_limit = (ENV['RACK_MULTIPART_TOTAL_PART_LIMIT'] || 4096).to_i
68
81
 
69
82
  def self.param_depth_limit
70
83
  default_query_parser.param_depth_limit
@@ -74,15 +87,6 @@ module Rack
74
87
  self.default_query_parser = self.default_query_parser.new_depth_limit(v)
75
88
  end
76
89
 
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
90
  if defined?(Process::CLOCK_MONOTONIC)
87
91
  def clock_time
88
92
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
@@ -121,19 +125,19 @@ module Rack
121
125
  }.join("&")
122
126
  when Hash
123
127
  value.map { |k, v|
124
- build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
128
+ build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
125
129
  }.delete_if(&:empty?).join('&')
126
130
  when nil
127
- prefix
131
+ escape(prefix)
128
132
  else
129
133
  raise ArgumentError, "value must be a Hash" if prefix.nil?
130
- "#{prefix}=#{escape(value)}"
134
+ "#{escape(prefix)}=#{escape(value)}"
131
135
  end
132
136
  end
133
137
 
134
138
  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)
139
+ q_value_header.to_s.split(',').map do |part|
140
+ value, parameters = part.split(';', 2).map(&:strip)
137
141
  quality = 1.0
138
142
  if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
139
143
  quality = md[1].to_f
@@ -142,16 +146,77 @@ module Rack
142
146
  end
143
147
  end
144
148
 
145
- def forwarded_values(forwarded_header)
146
- return nil unless forwarded_header
147
- forwarded_header = forwarded_header.to_s.gsub("\n", ";")
149
+ ALLOWED_FORWARDED_PARAMS = %w[by for host proto].map { |name| [name, name.to_sym] }.to_h.freeze
150
+ private_constant :ALLOWED_FORWARDED_PARAMS
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
- (values[$1.downcase.to_sym] ||= []) << $2
152
+ def forwarded_values(forwarded_header)
153
+ return unless forwarded_header
154
+ header = forwarded_header.to_s.tr("\n", ";")
155
+ header.sub!(/\A[\s;,]+/, '')
156
+ num_params = num_escapes = 0
157
+ max_params = max_escapes = 1024
158
+ params = {}
159
+
160
+ # Parse parameter list
161
+ while i = header.index('=')
162
+ # Only parse up to max parameters, to avoid potential denial of service
163
+ num_params += 1
164
+ return if num_params > max_params
165
+
166
+ # Found end of parameter name, ensure forward progress in loop
167
+ param = header.slice!(0, i+1)
168
+
169
+ # Remove ending equals and preceding whitespace from parameter name
170
+ param.chomp!('=')
171
+ param.strip!
172
+ param.downcase!
173
+ return unless param = ALLOWED_FORWARDED_PARAMS[param]
174
+
175
+ if header[0] == '"'
176
+ # Parameter value is quoted, parse it, handling backslash escapes
177
+ header.slice!(0, 1)
178
+ value = String.new
179
+
180
+ while i = header.index(/(["\\])/)
181
+ c = $1
182
+
183
+ # Append all content until ending quote or escape
184
+ value << header.slice!(0, i)
185
+
186
+ # Remove either backslash or ending quote,
187
+ # ensures forward progress in loop
188
+ header.slice!(0, 1)
189
+
190
+ # stop parsing parameter value if found ending quote
191
+ break if c == '"'
192
+
193
+ # Only allow up to max escapes, to avoid potential denial of service
194
+ num_escapes += 1
195
+ return if num_escapes > max_escapes
196
+ escaped_char = header.slice!(0, 1)
197
+ value << escaped_char
198
+ end
199
+ else
200
+ if i = header.index(/[;,]/)
201
+ # Parameter value unquoted (which may be invalid), value ends at comma or semicolon
202
+ value = header.slice!(0, i)
203
+ value.sub!(/[\s;,]+\z/, '')
204
+ else
205
+ # If no ending semicolon, assume remainder of line is value and stop parsing
206
+ header.strip!
207
+ value = header
208
+ header = ''
209
+ end
210
+ value.lstrip!
153
211
  end
212
+
213
+ (params[param] ||= []) << value
214
+
215
+ # skip trailing semicolons/commas/whitespace, to proceed to next parameter
216
+ header.sub!(/\A[\s;,]+/, '') unless header.empty?
154
217
  end
218
+
219
+ params
155
220
  end
156
221
  module_function :forwarded_values
157
222
 
@@ -172,33 +237,57 @@ module Rack
172
237
  matches&.first
173
238
  end
174
239
 
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
- # 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] }
240
+ # Introduced in ERB 4.0. ERB::Escape is an alias for ERB::Utils which
241
+ # doesn't get monkey-patched by rails
242
+ if defined?(ERB::Escape) && ERB::Escape.instance_method(:html_escape)
243
+ define_method(:escape_html, ERB::Escape.instance_method(:html_escape))
244
+ # :nocov:
245
+ # Ruby 3.2/ERB 4.0 added ERB::Escape#html_escape, so the else
246
+ # branch cannot be hit on the current Ruby version.
247
+ else
248
+ require 'cgi/escape'
249
+ # Escape ampersands, brackets and quotes to their HTML/XML entities.
250
+ def escape_html(string)
251
+ CGI.escapeHTML(string.to_s)
252
+ end
253
+ # :nocov:
189
254
  end
190
255
 
256
+ # Given an array of available encoding strings, and an array of
257
+ # acceptable encodings for a request, where each element of the
258
+ # acceptable encodings array is an array where the first element
259
+ # is an encoding name and the second element is the numeric
260
+ # priority for the encoding, return the available encoding with
261
+ # the highest priority.
262
+ #
263
+ # The accept_encoding argument is typically generated by calling
264
+ # Request#accept_encoding.
265
+ #
266
+ # Example:
267
+ #
268
+ # select_best_encoding(%w(compress gzip identity),
269
+ # [["compress", 0.5], ["gzip", 1.0]])
270
+ # # => "gzip"
271
+ #
272
+ # To reduce denial of service potential, only the first 16
273
+ # acceptable encodings are considered.
191
274
  def select_best_encoding(available_encodings, accept_encoding)
192
275
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
193
276
 
277
+ # Only process the first 16 encodings
278
+ accept_encoding = accept_encoding[0...16]
194
279
  expanded_accept_encoding = []
280
+ wildcard_seen = false
195
281
 
196
282
  accept_encoding.each do |m, q|
197
283
  preference = available_encodings.index(m) || available_encodings.size
198
284
 
199
285
  if m == "*"
200
- (available_encodings - accept_encoding.map(&:first)).each do |m2|
201
- expanded_accept_encoding << [m2, q, preference]
286
+ unless wildcard_seen
287
+ (available_encodings - accept_encoding.map(&:first)).each do |m2|
288
+ expanded_accept_encoding << [m2, q, preference]
289
+ end
290
+ wildcard_seen = true
202
291
  end
203
292
  else
204
293
  expanded_accept_encoding << [m, q, preference]
@@ -206,7 +295,13 @@ module Rack
206
295
  end
207
296
 
208
297
  encoding_candidates = expanded_accept_encoding
209
- .sort_by { |_, q, p| [-q, p] }
298
+ .sort do |(_, q1, p1), (_, q2, p2)|
299
+ if r = (q1 <=> q2).nonzero?
300
+ -r
301
+ else
302
+ (p1 <=> p2).nonzero? || 0
303
+ end
304
+ end
210
305
  .map!(&:first)
211
306
 
212
307
  unless encoding_candidates.include?("identity")
@@ -240,21 +335,6 @@ module Rack
240
335
  end
241
336
  end
242
337
 
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
338
  # :call-seq:
259
339
  # parse_cookies(env) -> hash
260
340
  #
@@ -268,23 +348,25 @@ module Rack
268
348
  parse_cookies_header env[HTTP_COOKIE]
269
349
  end
270
350
 
351
+ # A valid cookie key according to RFC6265 and RFC2616.
352
+ # 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: ( ) < > @ , ; : \ " / [ ] ? = { }.
353
+ VALID_COOKIE_KEY = /\A[!#$%&'*+\-\.\^_`|~0-9a-zA-Z]+\z/.freeze
354
+ private_constant :VALID_COOKIE_KEY
355
+
271
356
  # :call-seq:
272
357
  # set_cookie_header(key, value) -> encoded string
273
358
  #
274
359
  # Generate an encoded string using the provided +key+ and +value+ suitable
275
360
  # for the +set-cookie+ header according to RFC6265. The +value+ may be an
276
- # instance of either +String+ or +Hash+.
361
+ # instance of either +String+ or +Hash+. If the cookie key is invalid (as
362
+ # defined by RFC6265), an +ArgumentError+ will be raised.
277
363
  #
278
364
  # If the cookie +value+ is an instance of +Hash+, it considers the following
279
365
  # cookie attribute keys: +domain+, +max_age+, +expires+ (must be instance
280
366
  # of +Time+), +secure+, +http_only+, +same_site+ and +value+. For more
281
- # details about the interpretation of these fields, consult
367
+ # details about the interpretation of these fields, consult
282
368
  # [RFC6265 Section 5.2](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2).
283
369
  #
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
370
  # set_cookie_header("myname", "myvalue")
289
371
  # # => "myname=myvalue"
290
372
  #
@@ -292,9 +374,12 @@ module Rack
292
374
  # # => "myname=myvalue; max-age=10"
293
375
  #
294
376
  def set_cookie_header(key, value)
377
+ unless key =~ VALID_COOKIE_KEY
378
+ raise ArgumentError, "invalid cookie key: #{key.inspect}"
379
+ end
380
+
295
381
  case value
296
382
  when Hash
297
- key = escape(key) unless value[:escape_key] == false
298
383
  domain = "; domain=#{value[:domain]}" if value[:domain]
299
384
  path = "; path=#{value[:path]}" if value[:path]
300
385
  max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
@@ -306,23 +391,22 @@ module Rack
306
391
  when false, nil
307
392
  nil
308
393
  when :none, 'None', :None
309
- '; SameSite=None'
394
+ '; samesite=none'
310
395
  when :lax, 'Lax', :Lax
311
- '; SameSite=Lax'
396
+ '; samesite=lax'
312
397
  when true, :strict, 'Strict', :Strict
313
- '; SameSite=Strict'
398
+ '; samesite=strict'
314
399
  else
315
- raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
400
+ raise ArgumentError, "Invalid :same_site value: #{value[:same_site].inspect}"
316
401
  end
402
+ partitioned = "; partitioned" if value[:partitioned]
317
403
  value = value[:value]
318
- else
319
- key = escape(key)
320
404
  end
321
405
 
322
406
  value = [value] unless Array === value
323
407
 
324
408
  return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
325
- "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
409
+ "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}#{partitioned}"
326
410
  end
327
411
 
328
412
  # :call-seq:
@@ -363,24 +447,12 @@ module Rack
363
447
  set_cookie_header(key, value.merge(max_age: '0', expires: Time.at(0), value: ''))
364
448
  end
365
449
 
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
450
  def delete_cookie_header!(headers, key, value = {})
373
451
  headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value)
374
452
 
375
453
  return nil
376
454
  end
377
455
 
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
456
  # :call-seq:
385
457
  # delete_set_cookie_header!(header, key, value = {}) -> header value
386
458
  #
@@ -417,26 +489,31 @@ module Rack
417
489
  # Parses the "Range:" header, if present, into an array of Range objects.
418
490
  # Returns nil if the header is missing or syntactically invalid.
419
491
  # Returns an empty array if none of the ranges are satisfiable.
420
- def byte_ranges(env, size)
421
- get_byte_ranges env['HTTP_RANGE'], size
492
+ def byte_ranges(env, size, max_ranges: 100)
493
+ get_byte_ranges env['HTTP_RANGE'], size, max_ranges: max_ranges
422
494
  end
423
495
 
424
- def get_byte_ranges(http_range, size)
496
+ def get_byte_ranges(http_range, size, max_ranges: 100)
425
497
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
498
+ # Ignore Range when file size is 0 to avoid a 416 error.
499
+ return nil if size.zero?
426
500
  return nil unless http_range && http_range =~ /bytes=([^;]+)/
501
+ byte_range = $1
502
+ return nil if byte_range.count(',') >= max_ranges
427
503
  ranges = []
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?
504
+ byte_range.split(/,[ \t]*/).each do |range_spec|
505
+ return nil unless range_spec.include?('-')
506
+ range = range_spec.split('-')
507
+ r0, r1 = range[0], range[1]
508
+ if r0.nil? || r0.empty?
509
+ return nil if r1.nil?
433
510
  # suffix-byte-range-spec, represents trailing suffix of file
434
511
  r0 = size - r1.to_i
435
512
  r0 = 0 if r0 < 0
436
513
  r1 = size - 1
437
514
  else
438
515
  r0 = r0.to_i
439
- if r1.empty?
516
+ if r1.nil?
440
517
  r1 = size - 1
441
518
  else
442
519
  r1 = r1.to_i
@@ -446,6 +523,9 @@ module Rack
446
523
  end
447
524
  ranges << (r0..r1) if r0 <= r1
448
525
  end
526
+
527
+ return [] if ranges.map(&:size).sum > size
528
+
449
529
  ranges
450
530
  end
451
531
 
@@ -501,39 +581,12 @@ module Rack
501
581
  end
502
582
  end
503
583
 
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
584
  # Every standard HTTP code mapped to the appropriate message.
533
585
  # 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,"'
586
+ # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv \
587
+ # | ruby -rcsv -e "puts CSV.parse(STDIN, headers: true) \
588
+ # .reject {|v| v['Description'] == 'Unassigned' or v['Description'].include? '(' } \
589
+ # .map {|v| %Q/#{v['Value']} => '#{v['Description']}'/ }.join(','+?\n)"
537
590
  HTTP_STATUS_CODES = {
538
591
  100 => 'Continue',
539
592
  101 => 'Switching Protocols',
@@ -555,7 +608,6 @@ module Rack
555
608
  303 => 'See Other',
556
609
  304 => 'Not Modified',
557
610
  305 => 'Use Proxy',
558
- 306 => '(Unused)',
559
611
  307 => 'Temporary Redirect',
560
612
  308 => 'Permanent Redirect',
561
613
  400 => 'Bad Request',
@@ -571,13 +623,13 @@ module Rack
571
623
  410 => 'Gone',
572
624
  411 => 'Length Required',
573
625
  412 => 'Precondition Failed',
574
- 413 => 'Payload Too Large',
626
+ 413 => 'Content Too Large',
575
627
  414 => 'URI Too Long',
576
628
  415 => 'Unsupported Media Type',
577
629
  416 => 'Range Not Satisfiable',
578
630
  417 => 'Expectation Failed',
579
631
  421 => 'Misdirected Request',
580
- 422 => 'Unprocessable Entity',
632
+ 422 => 'Unprocessable Content',
581
633
  423 => 'Locked',
582
634
  424 => 'Failed Dependency',
583
635
  425 => 'Too Early',
@@ -585,7 +637,7 @@ module Rack
585
637
  428 => 'Precondition Required',
586
638
  429 => 'Too Many Requests',
587
639
  431 => 'Request Header Fields Too Large',
588
- 451 => 'Unavailable for Legal Reasons',
640
+ 451 => 'Unavailable For Legal Reasons',
589
641
  500 => 'Internal Server Error',
590
642
  501 => 'Not Implemented',
591
643
  502 => 'Bad Gateway',
@@ -595,8 +647,6 @@ module Rack
595
647
  506 => 'Variant Also Negotiates',
596
648
  507 => 'Insufficient Storage',
597
649
  508 => 'Loop Detected',
598
- 509 => 'Bandwidth Limit Exceeded',
599
- 510 => 'Not Extended',
600
650
  511 => 'Network Authentication Required'
601
651
  }
602
652
 
@@ -604,12 +654,34 @@ module Rack
604
654
  STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
605
655
 
606
656
  SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
607
- [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
657
+ [message.downcase.gsub(/\s|-/, '_').to_sym, code]
608
658
  }.flatten]
609
659
 
660
+ OBSOLETE_SYMBOLS_TO_STATUS_CODES = {
661
+ payload_too_large: 413,
662
+ unprocessable_entity: 422,
663
+ bandwidth_limit_exceeded: 509,
664
+ not_extended: 510
665
+ }.freeze
666
+ private_constant :OBSOLETE_SYMBOLS_TO_STATUS_CODES
667
+
668
+ OBSOLETE_SYMBOL_MAPPINGS = {
669
+ payload_too_large: :content_too_large,
670
+ unprocessable_entity: :unprocessable_content
671
+ }.freeze
672
+ private_constant :OBSOLETE_SYMBOL_MAPPINGS
673
+
610
674
  def status_code(status)
611
675
  if status.is_a?(Symbol)
612
- SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
676
+ SYMBOL_TO_STATUS_CODE.fetch(status) do
677
+ fallback_code = OBSOLETE_SYMBOLS_TO_STATUS_CODES.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
678
+ message = "Status code #{status.inspect} is deprecated and will be removed in a future version of Rack."
679
+ if canonical_symbol = OBSOLETE_SYMBOL_MAPPINGS[status]
680
+ message = "#{message} Please use #{canonical_symbol.inspect} instead."
681
+ end
682
+ warn message, uplevel: 3
683
+ fallback_code
684
+ end
613
685
  else
614
686
  status.to_i
615
687
  end
data/lib/rack/version.rb CHANGED
@@ -5,30 +5,13 @@
5
5
  # Rack is freely distributable under the terms of an MIT-style license.
6
6
  # See MIT-LICENSE or https://opensource.org/licenses/MIT.
7
7
 
8
- # The Rack main module, serving as a namespace for all core Rack
9
- # modules and classes.
10
- #
11
- # All modules meant for use in your application are <tt>autoload</tt>ed here,
12
- # so it should be enough just to <tt>require 'rack'</tt> in your code.
13
-
14
8
  module Rack
15
- # The Rack protocol version number implemented.
16
- VERSION = [1, 3].freeze
17
- deprecate_constant :VERSION
9
+ VERSION = "3.2.7"
18
10
 
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"
11
+ RELEASE = VERSION
29
12
 
30
13
  # Return the Rack release as a dotted string.
31
14
  def self.release
32
- RELEASE
15
+ VERSION
33
16
  end
34
17
  end
data/lib/rack.rb CHANGED
@@ -15,56 +15,50 @@ 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"
38
36
  autoload :Lock, "rack/lock"
39
- autoload :Logger, "rack/logger"
40
37
  autoload :MediaType, "rack/media_type"
41
38
  autoload :MethodOverride, "rack/method_override"
42
39
  autoload :Mime, "rack/mime"
40
+ autoload :MockRequest, "rack/mock_request"
41
+ autoload :MockResponse, "rack/mock_response"
42
+ autoload :Multipart, "rack/multipart"
43
43
  autoload :NullLogger, "rack/null_logger"
44
+ autoload :QueryParser, "rack/query_parser"
44
45
  autoload :Recursive, "rack/recursive"
45
46
  autoload :Reloader, "rack/reloader"
47
+ autoload :Request, "rack/request"
48
+ autoload :Response, "rack/response"
46
49
  autoload :RewindableInput, "rack/rewindable_input"
47
50
  autoload :Runtime, "rack/runtime"
48
51
  autoload :Sendfile, "rack/sendfile"
49
- autoload :Server, "rack/server"
50
52
  autoload :ShowExceptions, "rack/show_exceptions"
51
53
  autoload :ShowStatus, "rack/show_status"
52
54
  autoload :Static, "rack/static"
53
55
  autoload :TempfileReaper, "rack/tempfile_reaper"
54
56
  autoload :URLMap, "rack/urlmap"
55
57
  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
58
 
64
59
  module Auth
65
60
  autoload :Basic, "rack/auth/basic"
66
- autoload :AbstractRequest, "rack/auth/abstract/request"
67
61
  autoload :AbstractHandler, "rack/auth/abstract/handler"
68
- autoload :Digest, "rack/auth/digest"
62
+ autoload :AbstractRequest, "rack/auth/abstract/request"
69
63
  end
70
64
  end