1 # -*- encoding: binary -*-
2 # frozen_string_literal: true
10 require_relative 'query_parser'
11 require_relative 'mime'
12 require_relative 'headers'
15 # Rack::Utils contains a grab-bag of useful methods for writing web
16 # applications adopted from all kinds of Ruby libraries.
19 ParameterTypeError = QueryParser::ParameterTypeError
20 InvalidParameterError = QueryParser::InvalidParameterError
21 DEFAULT_SEP = QueryParser::DEFAULT_SEP
22 COMMON_SEP = QueryParser::COMMON_SEP
23 KeySpaceConstrainedParams = QueryParser::Params
26 attr_accessor :default_query_parser
28 # The default amount of nesting to allowed by hash parameters.
29 # This helps prevent a rogue client from triggering a possible stack overflow
30 # when parsing parameters.
31 self.default_query_parser = QueryParser.make_default(32)
35 # URI escapes. (CGI style space to +)
37 URI.encode_www_form_component(s)
40 # Like URI escaping, but with %20 instead of +. Strictly speaking this is
43 ::URI::DEFAULT_PARSER.escape s
46 # Unescapes the **path** component of a URI. See Rack::Utils.unescape for
47 # unescaping query parameters or form components.
49 ::URI::DEFAULT_PARSER.unescape s
52 # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
53 # target encoding of the string returned, and it defaults to UTF-8
54 def unescape(s, encoding = Encoding::UTF_8)
55 URI.decode_www_form_component(s, encoding)
59 attr_accessor :multipart_part_limit
62 # The maximum number of parts a request can contain. Accepting too many part
63 # can lead to the server running out of file handles.
64 # Set to `0` for no limit.
65 self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i
67 def self.param_depth_limit
68 default_query_parser.param_depth_limit
71 def self.param_depth_limit=(v)
72 self.default_query_parser = self.default_query_parser.new_depth_limit(v)
75 def self.key_space_limit
76 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)
80 def self.key_space_limit=(v)
81 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 if defined?(Process::CLOCK_MONOTONIC)
86 Process.clock_gettime(Process::CLOCK_MONOTONIC)
96 def parse_query(qs, d = nil, &unescaper)
97 Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
100 def parse_nested_query(qs, d = nil)
101 Rack::Utils.default_query_parser.parse_nested_query(qs, d)
104 def build_query(params)
107 build_query(v.map { |x| [k, x] })
109 v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}"
114 def build_nested_query(value, prefix = nil)
118 build_nested_query(v, "#{prefix}[]")
122 build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
123 }.delete_if(&:empty?).join('&')
127 raise ArgumentError, "value must be a Hash" if prefix.nil?
128 "#{prefix}=#{escape(value)}"
132 def q_values(q_value_header)
133 q_value_header.to_s.split(/\s*,\s*/).map do |part|
134 value, parameters = part.split(/\s*;\s*/, 2)
136 if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
143 def forwarded_values(forwarded_header)
144 return nil unless forwarded_header
145 forwarded_header = forwarded_header.to_s.gsub("\n", ";")
147 forwarded_header.split(/\s*;\s*/).each_with_object({}) do |field, values|
148 field.split(/\s*,\s*/).each do |pair|
149 return nil unless pair =~ /\A\s*(by|for|host|proto)\s*=\s*"?([^"]+)"?\s*\Z/i
150 (values[$1.downcase.to_sym] ||= []) << $2
154 module_function :forwarded_values
156 # Return best accept value to use, based on the algorithm
157 # in RFC 2616 Section 14. If there are multiple best
158 # matches (same specificity and quality), the value returned
160 def best_q_match(q_value_header, available_mimes)
161 values = q_values(q_value_header)
163 matches = values.map do |req_mime, quality|
164 match = available_mimes.find { |am| Rack::Mime.match?(am, req_mime) }
167 end.compact.sort_by do |match, quality|
168 (match.split('/', 2).count('*') * -10) + quality
182 ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
184 # Escape ampersands, brackets and quotes to their HTML/XML entities.
185 def escape_html(string)
186 string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
189 def select_best_encoding(available_encodings, accept_encoding)
190 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
192 expanded_accept_encoding = []
194 accept_encoding.each do |m, q|
195 preference = available_encodings.index(m) || available_encodings.size
198 (available_encodings - accept_encoding.map(&:first)).each do |m2|
199 expanded_accept_encoding << [m2, q, preference]
202 expanded_accept_encoding << [m, q, preference]
206 encoding_candidates = expanded_accept_encoding
207 .sort_by { |_, q, p| [-q, p] }
210 unless encoding_candidates.include?("identity")
211 encoding_candidates.push("identity")
214 expanded_accept_encoding.each do |m, q|
215 encoding_candidates.delete(m) if q == 0.0
218 (encoding_candidates & available_encodings)[0]
221 def parse_cookies(env)
222 parse_cookies_header env[HTTP_COOKIE]
225 def parse_cookies_header(header)
226 # According to RFC 6265:
227 # The syntax for cookie headers only supports semicolons
228 # User Agent -> Server ==
229 # Cookie: SID=31d4d96e407aad42; lang=en-US
230 return {} unless header
231 header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
232 next if cookie.empty?
233 key, value = cookie.split('=', 2)
234 cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
238 def add_cookie_to_header(header, key, value)
239 warn("add_cookie_to_header is deprecated and will be removed in Rack 3.1", uplevel: 1)
243 return set_cookie_header(key, value)
245 [header, set_cookie_header(key, value)]
247 header + [set_cookie_header(key, value)]
249 raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
253 def set_cookie_header(key, value)
256 key = escape(key) unless value[:escape_key] == false
257 domain = "; domain=#{value[:domain]}" if value[:domain]
258 path = "; path=#{value[:path]}" if value[:path]
259 max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
260 expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
261 secure = "; secure" if value[:secure]
262 httponly = "; httponly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
264 case value[:same_site]
267 when :none, 'None', :None
269 when :lax, 'Lax', :Lax
271 when true, :strict, 'Strict', :Strict
274 raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
276 value = value[:value]
280 value = [value] unless Array === value
282 return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
283 "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
286 def set_cookie_header!(headers, key, value)
287 if header = headers[SET_COOKIE]
288 if header.is_a?(Array)
289 header << set_cookie_header(key, value)
291 headers[SET_COOKIE] = [header, set_cookie_header(key, value)]
294 headers[SET_COOKIE] = set_cookie_header(key, value)
298 # Adds a cookie that will *remove* a cookie from the client. Hence the
299 # strange method name.
300 def delete_set_cookie_header(key, value = {})
301 set_cookie_header(key, {
302 value: '', path: nil, domain: nil,
308 def make_delete_cookie_header(header, key, value)
309 warn("make_delete_cookie_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
311 delete_set_cookie_header!(header, key, value)
314 def delete_cookie_header!(headers, key, value = {})
315 headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value)
320 def add_remove_cookie_to_header(header, key, value = {})
321 warn("add_remove_cookie_to_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
323 delete_set_cookie_header!(header, key, value)
326 def delete_set_cookie_header!(header, key, value = {})
328 header = Array(header)
329 header << delete_set_cookie_header(key, value)
331 header = delete_set_cookie_header(key, value)
341 # Parses the "Range:" header, if present, into an array of Range objects.
342 # Returns nil if the header is missing or syntactically invalid.
343 # Returns an empty array if none of the ranges are satisfiable.
344 def byte_ranges(env, size)
345 warn("`byte_ranges` is deprecated and will be removed in Rack 3.1, please use `get_byte_ranges`", uplevel: 1)
346 get_byte_ranges env['HTTP_RANGE'], size
349 def get_byte_ranges(http_range, size)
350 # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
351 return nil unless http_range && http_range =~ /bytes=([^;]+)/
353 $1.split(/,\s*/).each do |range_spec|
354 return nil unless range_spec =~ /(\d*)-(\d*)/
357 return nil if r1.empty?
358 # suffix-byte-range-spec, represents trailing suffix of file
368 return nil if r1 < r0 # backwards range is syntactically invalid
369 r1 = size - 1 if r1 >= size
372 ranges << (r0..r1) if r0 <= r1
377 # Constant time string comparison.
379 # NOTE: the values compared should be of fixed length, such as strings
380 # that have already been processed by HMAC. This should not be used
381 # on variable length plaintext strings because it could leak length info
382 # via timing attacks.
383 if defined?(OpenSSL.fixed_length_secure_compare)
384 def secure_compare(a, b)
385 return false unless a.bytesize == b.bytesize
387 OpenSSL.fixed_length_secure_compare(a, b)
390 def secure_compare(a, b)
391 return false unless a.bytesize == b.bytesize
396 b.each_byte { |v| r |= v ^ l[i += 1] }
401 # Context allows the use of a compatible middleware at different points
402 # in a request handling stack. A compatible middleware must define
403 # #context which should take the arguments env and app. The first of which
404 # would be the request environment. The second of which would be the rack
405 # application that the request would be forwarded to.
407 attr_reader :for, :app
409 def initialize(app_f, app_r)
410 raise 'running context does not respond to #context' unless app_f.respond_to? :context
411 @for, @app = app_f, app_r
415 @for.context(env, @app)
419 self.class.new(@for, app)
422 def context(env, app = @app)
423 recontext(app).call(env)
427 # A wrapper around Headers
431 class HeaderHash < Hash # :nodoc:
433 warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
434 if headers.is_a?(Headers) && !headers.frozen?
438 new_headers = Headers.new
439 headers.each{|k,v| new_headers[k] = v}
443 def self.new(hash = {})
444 warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
445 headers = Headers.new
446 hash.each{|k,v| headers[k] = v}
451 raise TypeError, "cannot allocate HeaderHash"
455 # Every standard HTTP code mapped to the appropriate message.
457 # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
458 # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
459 # puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
460 HTTP_STATUS_CODES = {
462 101 => 'Switching Protocols',
464 103 => 'Early Hints',
468 203 => 'Non-Authoritative Information',
470 205 => 'Reset Content',
471 206 => 'Partial Content',
472 207 => 'Multi-Status',
473 208 => 'Already Reported',
475 300 => 'Multiple Choices',
476 301 => 'Moved Permanently',
479 304 => 'Not Modified',
482 307 => 'Temporary Redirect',
483 308 => 'Permanent Redirect',
484 400 => 'Bad Request',
485 401 => 'Unauthorized',
486 402 => 'Payment Required',
489 405 => 'Method Not Allowed',
490 406 => 'Not Acceptable',
491 407 => 'Proxy Authentication Required',
492 408 => 'Request Timeout',
495 411 => 'Length Required',
496 412 => 'Precondition Failed',
497 413 => 'Payload Too Large',
498 414 => 'URI Too Long',
499 415 => 'Unsupported Media Type',
500 416 => 'Range Not Satisfiable',
501 417 => 'Expectation Failed',
502 421 => 'Misdirected Request',
503 422 => 'Unprocessable Entity',
505 424 => 'Failed Dependency',
507 426 => 'Upgrade Required',
508 428 => 'Precondition Required',
509 429 => 'Too Many Requests',
510 431 => 'Request Header Fields Too Large',
511 451 => 'Unavailable for Legal Reasons',
512 500 => 'Internal Server Error',
513 501 => 'Not Implemented',
514 502 => 'Bad Gateway',
515 503 => 'Service Unavailable',
516 504 => 'Gateway Timeout',
517 505 => 'HTTP Version Not Supported',
518 506 => 'Variant Also Negotiates',
519 507 => 'Insufficient Storage',
520 508 => 'Loop Detected',
521 509 => 'Bandwidth Limit Exceeded',
522 510 => 'Not Extended',
523 511 => 'Network Authentication Required'
526 # Responses with HTTP status codes that should not have an entity body
527 STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
529 SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
530 [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
533 def status_code(status)
534 if status.is_a?(Symbol)
535 SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
541 PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
543 def clean_path_info(path_info)
544 parts = path_info.split PATH_SEPS
549 next if part.empty? || part == '.'
550 part == '..' ? clean.pop : clean << part
553 clean_path = clean.join(::File::SEPARATOR)
554 clean_path.prepend("/") if parts.empty? || parts.first.empty?
560 def valid_path?(path)
561 path.valid_encoding? && !path.include?(NULL_BYTE)