Skip to content

Commit

Permalink
Update HTTP status codes and associated symbols (#2137)
Browse files Browse the repository at this point in the history
* Fix error message assertion

* Re-sync HTTP status codes and reason phrases

* Add fallback lookup and deprecation warning for symbols derived from obsolete or non-standard reason phrases
  • Loading branch information
wtn committed Dec 7, 2023
1 parent f6c583a commit 64ad26e
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 12 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ All notable changes to this project will be documented in this file. For info on
- Add `.mjs` MIME type ([#2057](https://github.com/rack/rack/pull/2057), [@axilleas])
- Update MIME types associated to `.ttf`, `.woff`, `.woff2` and `.otf` extensions to use mondern `font/*` types. ([#2065](https://github.com/rack/rack/pull/2065), [@davidstosik])
- `set_cookie_header` utility now supports the `partitioned` cookie attribute. This is required by Chrome in some embedded contexts. ([#2131](https://github.com/rack/rack/pull/2131), [@flavio-b])
- Remove non-standard status codes 306, 509, & 510 and update descriptions for 413, 422, & 451. ([#2137](https://github.com/rack/rack/pull/2137), [@wtn])
- Add fallback lookup and deprecation warning for obsolete status symbols. ([#2137](https://github.com/rack/rack/pull/2137), [@wtn])

## [3.0.8] - 2023-06-14

Expand Down
42 changes: 31 additions & 11 deletions lib/rack/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -468,9 +468,10 @@ def context(env, app = @app)

# Every standard HTTP code mapped to the appropriate message.
# Generated with:
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
# ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
# puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv \
# | ruby -rcsv -e "puts CSV.parse(STDIN, headers: true) \
# .reject {|v| v['Description'] == 'Unassigned' or v['Description'].include? '(' } \
# .map {|v| %Q/#{v['Value']} => '#{v['Description']}'/ }.join(','+?\n)"
HTTP_STATUS_CODES = {
100 => 'Continue',
101 => 'Switching Protocols',
Expand All @@ -492,7 +493,6 @@ def context(env, app = @app)
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
306 => '(Unused)',
307 => 'Temporary Redirect',
308 => 'Permanent Redirect',
400 => 'Bad Request',
Expand All @@ -508,21 +508,21 @@ def context(env, app = @app)
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Payload Too Large',
413 => 'Content Too Large',
414 => 'URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Range Not Satisfiable',
417 => 'Expectation Failed',
421 => 'Misdirected Request',
422 => 'Unprocessable Entity',
422 => 'Unprocessable Content',
423 => 'Locked',
424 => 'Failed Dependency',
425 => 'Too Early',
426 => 'Upgrade Required',
428 => 'Precondition Required',
429 => 'Too Many Requests',
431 => 'Request Header Fields Too Large',
451 => 'Unavailable for Legal Reasons',
451 => 'Unavailable For Legal Reasons',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
Expand All @@ -532,21 +532,41 @@ def context(env, app = @app)
506 => 'Variant Also Negotiates',
507 => 'Insufficient Storage',
508 => 'Loop Detected',
509 => 'Bandwidth Limit Exceeded',
510 => 'Not Extended',
511 => 'Network Authentication Required'
}

# Responses with HTTP status codes that should not have an entity body
STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]

SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
[message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
[message.downcase.gsub(/\s|-/, '_').to_sym, code]
}.flatten]

OBSOLETE_SYMBOLS_TO_STATUS_CODES = {
payload_too_large: 413,
unprocessable_entity: 422,
bandwidth_limit_exceeded: 509,
not_extended: 510
}.freeze
private_constant :OBSOLETE_SYMBOLS_TO_STATUS_CODES

OBSOLETE_SYMBOL_MAPPINGS = {
payload_too_large: :content_too_large,
unprocessable_entity: :unprocessable_content
}.freeze
private_constant :OBSOLETE_SYMBOL_MAPPINGS

def status_code(status)
if status.is_a?(Symbol)
SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
SYMBOL_TO_STATUS_CODE.fetch(status) do
fallback_code = OBSOLETE_SYMBOLS_TO_STATUS_CODES.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
message = "Status code #{status.inspect} is deprecated and will be removed in a future version of Rack."
if canonical_symbol = OBSOLETE_SYMBOL_MAPPINGS[status]
message = "#{message} Please use #{canonical_symbol.inspect} instead."
end
warn message, uplevel: 1
fallback_code
end
else
status.to_i
end
Expand Down
30 changes: 29 additions & 1 deletion test/spec_utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -528,10 +528,38 @@ def initialize(*)
Rack::Utils.status_code(:ok).must_equal 200
end

it "return status code and give deprecation warning for obsolete symbols" do
replaced_statuses = {
payload_too_large: {status_code: 413, standard_symbol: :content_too_large},
unprocessable_entity: {status_code: 422, standard_symbol: :unprocessable_content}
}
dropped_statuses = {bandwidth_limit_exceeded: 509, not_extended: 510}
verbose = $VERBOSE
warn_arg = nil
Rack::Utils.define_singleton_method(:warn) do |*args|
warn_arg = args
end
begin
$VERBOSE = true
replaced_statuses.each do |symbol, value_hash|
Rack::Utils.status_code(symbol).must_equal value_hash[:status_code]
warn_arg.must_equal ["Status code #{symbol.inspect} is deprecated and will be removed in a future version of Rack. Please use #{value_hash[:standard_symbol].inspect} instead.", { uplevel: 1 }]
end
dropped_statuses.each do |symbol, code|
Rack::Utils.status_code(symbol).must_equal code
warn_arg.must_equal ["Status code #{symbol.inspect} is deprecated and will be removed in a future version of Rack.", { uplevel: 1 }]
end
ensure
$VERBOSE = verbose
Rack::Utils.singleton_class.remove_method :warn
end
end

it "raise an error for an invalid symbol" do
assert_raises(ArgumentError, "Unrecognized status code :foobar") do
error = assert_raises(ArgumentError) do
Rack::Utils.status_code(:foobar)
end
error.message.must_equal "Unrecognized status code :foobar"
end

it "return rfc2822 format from rfc2822 helper" do
Expand Down

0 comments on commit 64ad26e

Please sign in to comment.