lib/rack/utils.rb in rack-3.0.10 vs lib/rack/utils.rb in rack-3.1.0
- old
+ new
@@ -4,10 +4,11 @@
require 'uri'
require 'fileutils'
require 'set'
require 'tempfile'
require 'time'
+require 'cgi/escape'
require_relative 'query_parser'
require_relative 'mime'
require_relative 'headers'
require_relative 'constants'
@@ -83,19 +84,10 @@
def self.param_depth_limit=(v)
self.default_query_parser = self.default_query_parser.new_depth_limit(v)
end
- def self.key_space_limit
- 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)
- 65536
- end
-
- def self.key_space_limit=(v)
- warn("`Rack::Utils.key_space_limit=` is deprecated and no longer has an effect. It will be removed in Rack 3.1", uplevel: 1)
- end
-
if defined?(Process::CLOCK_MONOTONIC)
def clock_time
Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
else
@@ -182,25 +174,12 @@
(match.split('/', 2).count('*') * -10) + quality
end.last
matches&.first
end
- ESCAPE_HTML = {
- "&" => "&",
- "<" => "<",
- ">" => ">",
- "'" => "'",
- '"' => """,
- "/" => "/"
- }
-
- ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
-
# Escape ampersands, brackets and quotes to their HTML/XML entities.
- def escape_html(string)
- string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
- end
+ define_method(:escape_html, CGI.method(:escapeHTML))
def select_best_encoding(available_encodings, accept_encoding)
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
expanded_accept_encoding = []
@@ -250,25 +229,10 @@
key, value = cookie.split('=', 2)
cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
end
end
- def add_cookie_to_header(header, key, value)
- warn("add_cookie_to_header is deprecated and will be removed in Rack 3.1", uplevel: 1)
-
- case header
- when nil, ''
- return set_cookie_header(key, value)
- when String
- [header, set_cookie_header(key, value)]
- when Array
- header + [set_cookie_header(key, value)]
- else
- raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
- end
- end
-
# :call-seq:
# parse_cookies(env) -> hash
#
# Parse cookies from the provided request environment using
# parse_cookies_header. Returns a map of cookie +key+ to cookie +value+.
@@ -278,10 +242,24 @@
#
def parse_cookies(env)
parse_cookies_header env[HTTP_COOKIE]
end
+ # A valid cookie key according to RFC2616.
+ # 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: ( ) < > @ , ; : \ " / [ ] ? = { }.
+ VALID_COOKIE_KEY = /\A[!#$%&'*+\-\.\^_`|~0-9a-zA-Z]+\z/.freeze
+ private_constant :VALID_COOKIE_KEY
+
+ private def escape_cookie_key(key)
+ if key =~ VALID_COOKIE_KEY
+ key
+ else
+ 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
+ escape(key)
+ end
+ end
+
# :call-seq:
# set_cookie_header(key, value) -> encoded string
#
# Generate an encoded string using the provided +key+ and +value+ suitable
# for the +set-cookie+ header according to RFC6265. The +value+ may be an
@@ -304,11 +282,11 @@
# # => "myname=myvalue; max-age=10"
#
def set_cookie_header(key, value)
case value
when Hash
- key = escape(key) unless value[:escape_key] == false
+ key = escape_cookie_key(key) unless value[:escape_key] == false
domain = "; domain=#{value[:domain]}" if value[:domain]
path = "; path=#{value[:path]}" if value[:path]
max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
secure = "; secure" if value[:secure]
@@ -316,27 +294,28 @@
same_site =
case value[:same_site]
when false, nil
nil
when :none, 'None', :None
- '; SameSite=None'
+ '; samesite=none'
when :lax, 'Lax', :Lax
- '; SameSite=Lax'
+ '; samesite=lax'
when true, :strict, 'Strict', :Strict
- '; SameSite=Strict'
+ '; samesite=strict'
else
- raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
+ raise ArgumentError, "Invalid :same_site value: #{value[:same_site].inspect}"
end
+ partitioned = "; partitioned" if value[:partitioned]
value = value[:value]
else
- key = escape(key)
+ key = escape_cookie_key(key)
end
value = [value] unless Array === value
return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
- "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
+ "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}#{partitioned}"
end
# :call-seq:
# set_cookie_header!(headers, key, value) -> header value
#
@@ -373,28 +352,16 @@
#
def delete_set_cookie_header(key, value = {})
set_cookie_header(key, value.merge(max_age: '0', expires: Time.at(0), value: ''))
end
- def make_delete_cookie_header(header, key, value)
- warn("make_delete_cookie_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
-
- delete_set_cookie_header!(header, key, value)
- end
-
def delete_cookie_header!(headers, key, value = {})
headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value)
return nil
end
- def add_remove_cookie_to_header(header, key, value = {})
- warn("add_remove_cookie_to_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
-
- delete_set_cookie_header!(header, key, value)
- end
-
# :call-seq:
# delete_set_cookie_header!(header, key, value = {}) -> header value
#
# Set an expired cookie in the specified headers with the given cookie
# +key+ and +value+ using delete_set_cookie_header. This causes
@@ -433,10 +400,12 @@
get_byte_ranges env['HTTP_RANGE'], size
end
def get_byte_ranges(http_range, size)
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
+ # Ignore Range when file size is 0 to avoid a 416 error.
+ return nil if size.zero?
return nil unless http_range && http_range =~ /bytes=([^;]+)/
ranges = []
$1.split(/,\s*/).each do |range_spec|
return nil unless range_spec.include?('-')
range = range_spec.split('-')
@@ -515,43 +484,16 @@
def context(env, app = @app)
recontext(app).call(env)
end
end
- # A wrapper around Headers
- # header when set.
- #
- # @api private
- class HeaderHash < Hash # :nodoc:
- def self.[](headers)
- warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
- if headers.is_a?(Headers) && !headers.frozen?
- return headers
- end
-
- new_headers = Headers.new
- headers.each{|k,v| new_headers[k] = v}
- new_headers
- end
-
- def self.new(hash = {})
- warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
- headers = Headers.new
- hash.each{|k,v| headers[k] = v}
- headers
- end
-
- def self.allocate
- raise TypeError, "cannot allocate HeaderHash"
- end
- end
-
# 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',
102 => 'Processing',
103 => 'Early Hints',
@@ -569,11 +511,10 @@
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
- 306 => '(Unused)',
307 => 'Temporary Redirect',
308 => 'Permanent Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
@@ -585,48 +526,68 @@
408 => 'Request Timeout',
409 => 'Conflict',
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',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
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
end