rack 3.0.5 → 3.2.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.

Potentially problematic release.


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

Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +383 -1
  3. data/CONTRIBUTING.md +11 -9
  4. data/README.md +95 -28
  5. data/SPEC.rdoc +206 -288
  6. data/lib/rack/auth/abstract/request.rb +2 -0
  7. data/lib/rack/auth/basic.rb +1 -2
  8. data/lib/rack/bad_request.rb +8 -0
  9. data/lib/rack/body_proxy.rb +18 -2
  10. data/lib/rack/builder.rb +29 -10
  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 +3 -1
  15. data/lib/rack/etag.rb +3 -0
  16. data/lib/rack/head.rb +2 -3
  17. data/lib/rack/headers.rb +86 -2
  18. data/lib/rack/lint.rb +482 -425
  19. data/lib/rack/media_type.rb +18 -9
  20. data/lib/rack/mime.rb +6 -5
  21. data/lib/rack/mock_request.rb +10 -15
  22. data/lib/rack/mock_response.rb +39 -18
  23. data/lib/rack/multipart/parser.rb +169 -73
  24. data/lib/rack/multipart/uploaded_file.rb +42 -5
  25. data/lib/rack/multipart.rb +9 -1
  26. data/lib/rack/query_parser.rb +86 -97
  27. data/lib/rack/request.rb +67 -100
  28. data/lib/rack/response.rb +29 -19
  29. data/lib/rack/rewindable_input.rb +4 -1
  30. data/lib/rack/sendfile.rb +2 -2
  31. data/lib/rack/show_exceptions.rb +10 -4
  32. data/lib/rack/show_status.rb +0 -2
  33. data/lib/rack/static.rb +2 -1
  34. data/lib/rack/utils.rb +78 -110
  35. data/lib/rack/version.rb +3 -20
  36. data/lib/rack.rb +11 -17
  37. metadata +6 -15
  38. data/lib/rack/auth/digest/md5.rb +0 -1
  39. data/lib/rack/auth/digest/nonce.rb +0 -1
  40. data/lib/rack/auth/digest/params.rb +0 -1
  41. data/lib/rack/auth/digest/request.rb +0 -1
  42. data/lib/rack/auth/digest.rb +0 -256
  43. data/lib/rack/chunked.rb +0 -120
  44. data/lib/rack/file.rb +0 -9
  45. data/lib/rack/logger.rb +0 -22
data/lib/rack/response.rb CHANGED
@@ -31,13 +31,6 @@ module Rack
31
31
  attr_accessor :length, :status, :body
32
32
  attr_reader :headers
33
33
 
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
34
  # Initialize the response object with the specified +body+, +status+
42
35
  # and +headers+.
43
36
  #
@@ -62,7 +55,7 @@ module Rack
62
55
  @status = status.to_i
63
56
 
64
57
  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
58
+ raise ArgumentError, "Headers must be a Hash!"
66
59
  end
67
60
 
68
61
  @headers = Headers.new
@@ -79,7 +72,8 @@ module Rack
79
72
  if body.nil?
80
73
  @body = []
81
74
  @buffered = true
82
- @length = 0
75
+ # Body is unspecified - it may be a buffered response, or it may be a HEAD response.
76
+ @length = nil
83
77
  elsif body.respond_to?(:to_str)
84
78
  @body = [body]
85
79
  @buffered = true
@@ -87,7 +81,7 @@ module Rack
87
81
  else
88
82
  @body = body
89
83
  @buffered = nil # undetermined as of yet.
90
- @length = 0
84
+ @length = nil
91
85
  end
92
86
 
93
87
  yield self if block_given?
@@ -106,7 +100,7 @@ module Rack
106
100
  # The response body is an enumerable body and it is not allowed to have an entity body.
107
101
  @body.respond_to?(:each) && STATUS_WITH_NO_ENTITY_BODY[@status]
108
102
  end
109
-
103
+
110
104
  # Generate a response array consistent with the requirements of the SPEC.
111
105
  # @return [Array] a 3-tuple suitable of `[status, headers, body]`
112
106
  # which is suitable to be returned from the middleware `#call(env)` method.
@@ -118,9 +112,14 @@ module Rack
118
112
  return [@status, @headers, []]
119
113
  else
120
114
  if block_given?
115
+ # We don't add the content-length here as the user has provided a block that can #write additional chunks to the body.
121
116
  @block = block
122
117
  return [@status, @headers, self]
123
118
  else
119
+ # If we know the length of the body, set the content-length header... except if we are chunked? which is a legacy special case where the body might already be encoded and thus the actual encoded body length and the content-length are likely to be different.
120
+ if @length && !chunked?
121
+ @headers[CONTENT_LENGTH] = @length.to_s
122
+ end
124
123
  return [@status, @headers, @body]
125
124
  end
126
125
  end
@@ -138,7 +137,9 @@ module Rack
138
137
  end
139
138
  end
140
139
 
141
- # Append to body and update content-length.
140
+ # Append a chunk to the response body.
141
+ #
142
+ # Converts the response into a buffered response if it wasn't already.
142
143
  #
143
144
  # NOTE: Do not mix #write and direct #body access!
144
145
  #
@@ -320,27 +321,34 @@ module Rack
320
321
 
321
322
  protected
322
323
 
324
+ # Convert the body of this response into an internally buffered Array if possible.
325
+ #
326
+ # `@buffered` is a ternary value which indicates whether the body is buffered. It can be:
327
+ # * `nil` - The body has not been buffered yet.
328
+ # * `true` - The body is buffered as an Array instance.
329
+ # * `false` - The body is not buffered and cannot be buffered.
330
+ #
331
+ # @return [Boolean] whether the body is buffered as an Array instance.
323
332
  def buffered_body!
324
333
  if @buffered.nil?
325
334
  if @body.is_a?(Array)
326
335
  # The user supplied body was an array:
327
336
  @body = @body.compact
328
- @body.each do |part|
329
- @length += part.to_s.bytesize
330
- end
337
+ @length = @body.sum{|part| part.bytesize}
338
+ @buffered = true
331
339
  elsif @body.respond_to?(:each)
332
340
  # Turn the user supplied body into a buffered array:
333
341
  body = @body
334
342
  @body = Array.new
343
+ @buffered = true
335
344
 
336
345
  body.each do |part|
337
346
  @writer.call(part.to_s)
338
347
  end
339
348
 
340
349
  body.close if body.respond_to?(:close)
341
-
342
- @buffered = true
343
350
  else
351
+ # We don't know how to buffer the user-supplied body:
344
352
  @buffered = false
345
353
  end
346
354
  end
@@ -349,11 +357,13 @@ module Rack
349
357
  end
350
358
 
351
359
  def append(chunk)
360
+ chunk = chunk.dup unless chunk.frozen?
352
361
  @body << chunk
353
362
 
354
- unless chunked?
363
+ if @length
355
364
  @length += chunk.bytesize
356
- set_header(CONTENT_LENGTH, @length.to_s)
365
+ elsif @buffered
366
+ @length = chunk.bytesize
357
367
  end
358
368
 
359
369
  return chunk
@@ -21,7 +21,10 @@ module Rack
21
21
  end
22
22
 
23
23
  def call(env)
24
- env[RACK_INPUT] = RewindableInput.new(env[RACK_INPUT])
24
+ if (input = env[RACK_INPUT])
25
+ env[RACK_INPUT] = RewindableInput.new(input)
26
+ end
27
+
25
28
  @app.call(env)
26
29
  end
27
30
  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)
@@ -138,7 +138,7 @@ module Rack
138
138
  end
139
139
  when '', nil
140
140
  else
141
- env[RACK_ERRORS].puts "Unknown x-sendfile variation: '#{type}'.\n"
141
+ env[RACK_ERRORS].puts "Unknown x-sendfile variation: #{type.inspect}"
142
142
  end
143
143
  end
144
144
  response
@@ -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
@@ -61,8 +65,12 @@ module Rack
61
65
  def dump_exception(exception)
62
66
  if exception.respond_to?(:detailed_message)
63
67
  message = exception.detailed_message(highlight: false)
68
+ # :nocov:
69
+ # Ruby 3.2 added Exception#detailed_message, so the else
70
+ # branch cannot be hit on the current Ruby version.
64
71
  else
65
72
  message = exception.message
73
+ # :nocov:
66
74
  end
67
75
  string = "#{exception.class}: #{message}\n".dup
68
76
  string << exception.backtrace.map { |l| "\t#{l}" }.join("\n")
@@ -79,7 +87,7 @@ module Rack
79
87
  # This double assignment is to prevent an "unused variable" warning.
80
88
  # Yes, it is dumb, but I don't like Ruby yelling at me.
81
89
  frames = frames = exception.backtrace.map { |line|
82
- frame = OpenStruct.new
90
+ frame = Frame.new
83
91
  if line =~ /(.*?):(\d+)(:in `(.*)')?/
84
92
  frame.filename = $1
85
93
  frame.lineno = $2.to_i
@@ -397,7 +405,5 @@ module Rack
397
405
  </body>
398
406
  </html>
399
407
  HTML
400
-
401
- # :startdoc:
402
408
  end
403
409
  end
@@ -117,7 +117,5 @@ TEMPLATE = <<'HTML'
117
117
  </body>
118
118
  </html>
119
119
  HTML
120
-
121
- # :startdoc:
122
120
  end
123
121
  end
data/lib/rack/static.rb CHANGED
@@ -124,8 +124,9 @@ module Rack
124
124
 
125
125
  def call(env)
126
126
  path = env[PATH_INFO]
127
+ actual_path = Utils.clean_path_info(Utils.unescape_path(path))
127
128
 
128
- if can_serve(path)
129
+ if can_serve(actual_path)
129
130
  if overwrite_file_path(path)
130
131
  env[PATH_INFO] = (add_index_root?(path) ? path + @index : @urls[path])
131
132
  elsif @gzip && env['HTTP_ACCEPT_ENCODING'] && /\bgzip\b/.match?(env['HTTP_ACCEPT_ENCODING'])
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
@@ -85,15 +87,6 @@ module Rack
85
87
  self.default_query_parser = self.default_query_parser.new_depth_limit(v)
86
88
  end
87
89
 
88
- def self.key_space_limit
89
- warn("`Rack::Utils.key_space_limit` is deprecated as this value no longer has an effect. It will be removed in Rack 3.1", uplevel: 1)
90
- 65536
91
- end
92
-
93
- def self.key_space_limit=(v)
94
- warn("`Rack::Utils.key_space_limit=` is deprecated and no longer has an effect. It will be removed in Rack 3.1", uplevel: 1)
95
- end
96
-
97
90
  if defined?(Process::CLOCK_MONOTONIC)
98
91
  def clock_time
99
92
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
@@ -143,8 +136,8 @@ module Rack
143
136
  end
144
137
 
145
138
  def q_values(q_value_header)
146
- q_value_header.to_s.split(/\s*,\s*/).map do |part|
147
- 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)
148
141
  quality = 1.0
149
142
  if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
150
143
  quality = md[1].to_f
@@ -157,9 +150,10 @@ module Rack
157
150
  return nil unless forwarded_header
158
151
  forwarded_header = forwarded_header.to_s.gsub("\n", ";")
159
152
 
160
- forwarded_header.split(/\s*;\s*/).each_with_object({}) do |field, values|
161
- field.split(/\s*,\s*/).each do |pair|
162
- return nil unless pair =~ /\A\s*(by|for|host|proto)\s*=\s*"?([^"]+)"?\s*\Z/i
153
+ forwarded_header.split(';').each_with_object({}) do |field, values|
154
+ field.split(',').each do |pair|
155
+ pair = pair.split('=').map(&:strip).join('=')
156
+ return nil unless pair =~ /\A(by|for|host|proto)="?([^"]+)"?\Z/i
163
157
  (values[$1.downcase.to_sym] ||= []) << $2
164
158
  end
165
159
  end
@@ -183,20 +177,20 @@ module Rack
183
177
  matches&.first
184
178
  end
185
179
 
186
- ESCAPE_HTML = {
187
- "&" => "&amp;",
188
- "<" => "&lt;",
189
- ">" => "&gt;",
190
- "'" => "&#x27;",
191
- '"' => "&quot;",
192
- "/" => "&#x2F;"
193
- }
194
-
195
- ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
196
-
197
- # Escape ampersands, brackets and quotes to their HTML/XML entities.
198
- def escape_html(string)
199
- string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
180
+ # Introduced in ERB 4.0. ERB::Escape is an alias for ERB::Utils which
181
+ # doesn't get monkey-patched by rails
182
+ if defined?(ERB::Escape) && ERB::Escape.instance_method(:html_escape)
183
+ define_method(:escape_html, ERB::Escape.instance_method(:html_escape))
184
+ # :nocov:
185
+ # Ruby 3.2/ERB 4.0 added ERB::Escape#html_escape, so the else
186
+ # branch cannot be hit on the current Ruby version.
187
+ else
188
+ require 'cgi/escape'
189
+ # Escape ampersands, brackets and quotes to their HTML/XML entities.
190
+ def escape_html(string)
191
+ CGI.escapeHTML(string.to_s)
192
+ end
193
+ # :nocov:
200
194
  end
201
195
 
202
196
  def select_best_encoding(available_encodings, accept_encoding)
@@ -251,21 +245,6 @@ module Rack
251
245
  end
252
246
  end
253
247
 
254
- def add_cookie_to_header(header, key, value)
255
- warn("add_cookie_to_header is deprecated and will be removed in Rack 3.1", uplevel: 1)
256
-
257
- case header
258
- when nil, ''
259
- return set_cookie_header(key, value)
260
- when String
261
- [header, set_cookie_header(key, value)]
262
- when Array
263
- header + [set_cookie_header(key, value)]
264
- else
265
- raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
266
- end
267
- end
268
-
269
248
  # :call-seq:
270
249
  # parse_cookies(env) -> hash
271
250
  #
@@ -279,12 +258,18 @@ module Rack
279
258
  parse_cookies_header env[HTTP_COOKIE]
280
259
  end
281
260
 
261
+ # A valid cookie key according to RFC6265 and RFC2616.
262
+ # 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: ( ) < > @ , ; : \ " / [ ] ? = { }.
263
+ VALID_COOKIE_KEY = /\A[!#$%&'*+\-\.\^_`|~0-9a-zA-Z]+\z/.freeze
264
+ private_constant :VALID_COOKIE_KEY
265
+
282
266
  # :call-seq:
283
267
  # set_cookie_header(key, value) -> encoded string
284
268
  #
285
269
  # Generate an encoded string using the provided +key+ and +value+ suitable
286
270
  # for the +set-cookie+ header according to RFC6265. The +value+ may be an
287
- # instance of either +String+ or +Hash+.
271
+ # instance of either +String+ or +Hash+. If the cookie key is invalid (as
272
+ # defined by RFC6265), an +ArgumentError+ will be raised.
288
273
  #
289
274
  # If the cookie +value+ is an instance of +Hash+, it considers the following
290
275
  # cookie attribute keys: +domain+, +max_age+, +expires+ (must be instance
@@ -292,10 +277,6 @@ module Rack
292
277
  # details about the interpretation of these fields, consult
293
278
  # [RFC6265 Section 5.2](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2).
294
279
  #
295
- # An extra cookie attribute +escape_key+ can be provided to control whether
296
- # or not the cookie key is URL encoded. If explicitly set to +false+, the
297
- # cookie key name will not be url encoded (escaped). The default is +true+.
298
- #
299
280
  # set_cookie_header("myname", "myvalue")
300
281
  # # => "myname=myvalue"
301
282
  #
@@ -303,9 +284,12 @@ module Rack
303
284
  # # => "myname=myvalue; max-age=10"
304
285
  #
305
286
  def set_cookie_header(key, value)
287
+ unless key =~ VALID_COOKIE_KEY
288
+ raise ArgumentError, "invalid cookie key: #{key.inspect}"
289
+ end
290
+
306
291
  case value
307
292
  when Hash
308
- key = escape(key) unless value[:escape_key] == false
309
293
  domain = "; domain=#{value[:domain]}" if value[:domain]
310
294
  path = "; path=#{value[:path]}" if value[:path]
311
295
  max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
@@ -317,23 +301,22 @@ module Rack
317
301
  when false, nil
318
302
  nil
319
303
  when :none, 'None', :None
320
- '; SameSite=None'
304
+ '; samesite=none'
321
305
  when :lax, 'Lax', :Lax
322
- '; SameSite=Lax'
306
+ '; samesite=lax'
323
307
  when true, :strict, 'Strict', :Strict
324
- '; SameSite=Strict'
308
+ '; samesite=strict'
325
309
  else
326
- raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
310
+ raise ArgumentError, "Invalid :same_site value: #{value[:same_site].inspect}"
327
311
  end
312
+ partitioned = "; partitioned" if value[:partitioned]
328
313
  value = value[:value]
329
- else
330
- key = escape(key)
331
314
  end
332
315
 
333
316
  value = [value] unless Array === value
334
317
 
335
318
  return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
336
- "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
319
+ "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}#{partitioned}"
337
320
  end
338
321
 
339
322
  # :call-seq:
@@ -374,24 +357,12 @@ module Rack
374
357
  set_cookie_header(key, value.merge(max_age: '0', expires: Time.at(0), value: ''))
375
358
  end
376
359
 
377
- def make_delete_cookie_header(header, key, value)
378
- warn("make_delete_cookie_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
379
-
380
- delete_set_cookie_header!(header, key, value)
381
- end
382
-
383
360
  def delete_cookie_header!(headers, key, value = {})
384
361
  headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value)
385
362
 
386
363
  return nil
387
364
  end
388
365
 
389
- def add_remove_cookie_to_header(header, key, value = {})
390
- warn("add_remove_cookie_to_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
391
-
392
- delete_set_cookie_header!(header, key, value)
393
- end
394
-
395
366
  # :call-seq:
396
367
  # delete_set_cookie_header!(header, key, value = {}) -> header value
397
368
  #
@@ -434,9 +405,11 @@ module Rack
434
405
 
435
406
  def get_byte_ranges(http_range, size)
436
407
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
408
+ # Ignore Range when file size is 0 to avoid a 416 error.
409
+ return nil if size.zero?
437
410
  return nil unless http_range && http_range =~ /bytes=([^;]+)/
438
411
  ranges = []
439
- $1.split(/,\s*/).each do |range_spec|
412
+ $1.split(/,[ \t]*/).each do |range_spec|
440
413
  return nil unless range_spec.include?('-')
441
414
  range = range_spec.split('-')
442
415
  r0, r1 = range[0], range[1]
@@ -458,6 +431,9 @@ module Rack
458
431
  end
459
432
  ranges << (r0..r1) if r0 <= r1
460
433
  end
434
+
435
+ return [] if ranges.map(&:size).sum > size
436
+
461
437
  ranges
462
438
  end
463
439
 
@@ -513,39 +489,12 @@ module Rack
513
489
  end
514
490
  end
515
491
 
516
- # A wrapper around Headers
517
- # header when set.
518
- #
519
- # @api private
520
- class HeaderHash < Hash # :nodoc:
521
- def self.[](headers)
522
- warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
523
- if headers.is_a?(Headers) && !headers.frozen?
524
- return headers
525
- end
526
-
527
- new_headers = Headers.new
528
- headers.each{|k,v| new_headers[k] = v}
529
- new_headers
530
- end
531
-
532
- def self.new(hash = {})
533
- warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
534
- headers = Headers.new
535
- hash.each{|k,v| headers[k] = v}
536
- headers
537
- end
538
-
539
- def self.allocate
540
- raise TypeError, "cannot allocate HeaderHash"
541
- end
542
- end
543
-
544
492
  # Every standard HTTP code mapped to the appropriate message.
545
493
  # Generated with:
546
- # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
547
- # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
548
- # puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
494
+ # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv \
495
+ # | ruby -rcsv -e "puts CSV.parse(STDIN, headers: true) \
496
+ # .reject {|v| v['Description'] == 'Unassigned' or v['Description'].include? '(' } \
497
+ # .map {|v| %Q/#{v['Value']} => '#{v['Description']}'/ }.join(','+?\n)"
549
498
  HTTP_STATUS_CODES = {
550
499
  100 => 'Continue',
551
500
  101 => 'Switching Protocols',
@@ -567,7 +516,6 @@ module Rack
567
516
  303 => 'See Other',
568
517
  304 => 'Not Modified',
569
518
  305 => 'Use Proxy',
570
- 306 => '(Unused)',
571
519
  307 => 'Temporary Redirect',
572
520
  308 => 'Permanent Redirect',
573
521
  400 => 'Bad Request',
@@ -583,13 +531,13 @@ module Rack
583
531
  410 => 'Gone',
584
532
  411 => 'Length Required',
585
533
  412 => 'Precondition Failed',
586
- 413 => 'Payload Too Large',
534
+ 413 => 'Content Too Large',
587
535
  414 => 'URI Too Long',
588
536
  415 => 'Unsupported Media Type',
589
537
  416 => 'Range Not Satisfiable',
590
538
  417 => 'Expectation Failed',
591
539
  421 => 'Misdirected Request',
592
- 422 => 'Unprocessable Entity',
540
+ 422 => 'Unprocessable Content',
593
541
  423 => 'Locked',
594
542
  424 => 'Failed Dependency',
595
543
  425 => 'Too Early',
@@ -597,7 +545,7 @@ module Rack
597
545
  428 => 'Precondition Required',
598
546
  429 => 'Too Many Requests',
599
547
  431 => 'Request Header Fields Too Large',
600
- 451 => 'Unavailable for Legal Reasons',
548
+ 451 => 'Unavailable For Legal Reasons',
601
549
  500 => 'Internal Server Error',
602
550
  501 => 'Not Implemented',
603
551
  502 => 'Bad Gateway',
@@ -607,8 +555,6 @@ module Rack
607
555
  506 => 'Variant Also Negotiates',
608
556
  507 => 'Insufficient Storage',
609
557
  508 => 'Loop Detected',
610
- 509 => 'Bandwidth Limit Exceeded',
611
- 510 => 'Not Extended',
612
558
  511 => 'Network Authentication Required'
613
559
  }
614
560
 
@@ -616,12 +562,34 @@ module Rack
616
562
  STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
617
563
 
618
564
  SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
619
- [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
565
+ [message.downcase.gsub(/\s|-/, '_').to_sym, code]
620
566
  }.flatten]
621
567
 
568
+ OBSOLETE_SYMBOLS_TO_STATUS_CODES = {
569
+ payload_too_large: 413,
570
+ unprocessable_entity: 422,
571
+ bandwidth_limit_exceeded: 509,
572
+ not_extended: 510
573
+ }.freeze
574
+ private_constant :OBSOLETE_SYMBOLS_TO_STATUS_CODES
575
+
576
+ OBSOLETE_SYMBOL_MAPPINGS = {
577
+ payload_too_large: :content_too_large,
578
+ unprocessable_entity: :unprocessable_content
579
+ }.freeze
580
+ private_constant :OBSOLETE_SYMBOL_MAPPINGS
581
+
622
582
  def status_code(status)
623
583
  if status.is_a?(Symbol)
624
- SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
584
+ SYMBOL_TO_STATUS_CODE.fetch(status) do
585
+ fallback_code = OBSOLETE_SYMBOLS_TO_STATUS_CODES.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
586
+ message = "Status code #{status.inspect} is deprecated and will be removed in a future version of Rack."
587
+ if canonical_symbol = OBSOLETE_SYMBOL_MAPPINGS[status]
588
+ message = "#{message} Please use #{canonical_symbol.inspect} instead."
589
+ end
590
+ warn message, uplevel: 3
591
+ fallback_code
592
+ end
625
593
  else
626
594
  status.to_i
627
595
  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.0"
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.5"
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