123456789_123456789_123456789_123456789_123456789_

Module: Rack::Utils

Relationships & Source Files
Namespace Children
Classes:
Extension / Inclusion / Inheritance Descendants
Included In:
Defined in: lib/rack/utils.rb

Overview

Utils contains a grab-bag of useful methods for writing web applications adopted from all kinds of Ruby libraries.

Constant Summary

  • COMMON_SEP =
    # File 'lib/rack/utils.rb', line 25
    QueryParser::COMMON_SEP
  • DEFAULT_SEP =
    # File 'lib/rack/utils.rb', line 24
    QueryParser::DEFAULT_SEP
  • HTTP_STATUS_CODES =

    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 -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)"
    # File 'lib/rack/utils.rb', line 498
    {
      100 => 'Continue',
      101 => 'Switching Protocols',
      102 => 'Processing',
      103 => 'Early Hints',
      200 => 'OK',
      201 => 'Created',
      202 => 'Accepted',
      203 => 'Non-Authoritative Information',
      204 => 'No Content',
      205 => 'Reset Content',
      206 => 'Partial Content',
      207 => 'Multi-Status',
      208 => 'Already Reported',
      226 => 'IM Used',
      300 => 'Multiple Choices',
      301 => 'Moved Permanently',
      302 => 'Found',
      303 => 'See Other',
      304 => 'Not Modified',
      305 => 'Use Proxy',
      307 => 'Temporary Redirect',
      308 => 'Permanent Redirect',
      400 => 'Bad Request',
      401 => 'Unauthorized',
      402 => 'Payment Required',
      403 => 'Forbidden',
      404 => 'Not Found',
      405 => 'Method Not Allowed',
      406 => 'Not Acceptable',
      407 => 'Proxy Authentication Required',
      408 => 'Request Timeout',
      409 => 'Conflict',
      410 => 'Gone',
      411 => 'Length Required',
      412 => 'Precondition Failed',
      413 => 'Content Too Large',
      414 => 'URI Too Long',
      415 => 'Unsupported Media Type',
      416 => 'Range Not Satisfiable',
      417 => 'Expectation Failed',
      421 => 'Misdirected Request',
      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',
      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',
      511 => 'Network Authentication Required'
    }
  • InvalidParameterError =
    # File 'lib/rack/utils.rb', line 22
    QueryParser::InvalidParameterError
  • KeySpaceConstrainedParams =
    # File 'lib/rack/utils.rb', line 26
    QueryParser::Params
  • NULL_BYTE =
    # File 'lib/rack/utils.rb', line 615
    "\0"
  • OBSOLETE_SYMBOLS_TO_STATUS_CODES = private
    # File 'lib/rack/utils.rb', line 568
    {
      payload_too_large: 413,
      unprocessable_entity: 422,
      bandwidth_limit_exceeded: 509,
      not_extended: 510
    }.freeze
  • OBSOLETE_SYMBOL_MAPPINGS = private
    # File 'lib/rack/utils.rb', line 576
    {
      payload_too_large: :content_too_large,
      unprocessable_entity: :unprocessable_content
    }.freeze
  • PATH_SEPS =
    # File 'lib/rack/utils.rb', line 598
    Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
  • ParameterTypeError =
    # File 'lib/rack/utils.rb', line 21
    QueryParser::ParameterTypeError
  • ParamsTooDeepError =
    # File 'lib/rack/utils.rb', line 23
    QueryParser::ParamsTooDeepError
  • STATUS_WITH_NO_ENTITY_BODY =

    Responses with HTTP status codes that should not have an entity body

    # File 'lib/rack/utils.rb', line 562
    
      
  • SYMBOL_TO_STATUS_CODE =
    # File 'lib/rack/utils.rb', line 564
    
      
  • URI_PARSER =
    # File 'lib/rack/utils.rb', line 27
    defined?(::URI::RFC2396_PARSER) ? ::URI::RFC2396_PARSER : ::URI::DEFAULT_PARSER
  • VALID_COOKIE_KEY = private

    A valid cookie key according to RFC6265 and 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: ( ) < > @ , ; : \ “ / [ ] ? = { }.

    # File 'lib/rack/utils.rb', line 263
    /\A[!#$%&'*\-\.\^_`|~0-9a-zA-Z]\z/.freeze

Class Attribute Summary

Class Method Summary

Class Attribute Details

.default_query_parser (rw)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 30

attr_accessor :default_query_parser

.multipart_file_limit (rw) Also known as: .multipart_part_limit

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 65

attr_accessor :multipart_file_limit

.multipart_part_limit (rw)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 69

alias multipart_part_limit multipart_file_limit

.multipart_total_part_limit (rw)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 63

attr_accessor :multipart_total_part_limit

.param_depth_limit (rw)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 82

def self.param_depth_limit
  default_query_parser.param_depth_limit
end

.param_depth_limit=(v) (rw)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 86

def self.param_depth_limit=(v)
  self.default_query_parser = self.default_query_parser.new_depth_limit(v)
end

Class Method Details

.best_q_match(q_value_header, available_mimes) (mod_func)

Return best accept value to use, based on the algorithm in RFC 2616 Section 14. If there are multiple best matches (same specificity and quality), the value returned is arbitrary.

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 167

def best_q_match(q_value_header, available_mimes)
  values = q_values(q_value_header)

  matches = values.map do |req_mime, quality|
    match = available_mimes.find { |am| Rack::Mime.match?(am, req_mime) }
    next unless match
    [match, quality]
  end.compact.sort_by do |match, quality|
    (match.split('/', 2).count('*') * -10) + quality
  end.last
  matches&.first
end

.build_nested_query(value, prefix = nil) (mod_func)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 120

def build_nested_query(value, prefix = nil)
  case value
  when Array
    value.map { |v|
      build_nested_query(v, "#{prefix}[]")
    }.join("&")
  when Hash
    value.map { |k, v|
      build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
    }.delete_if(&:empty?).join('&')
  when nil
    escape(prefix)
  else
    raise ArgumentError, "value must be a Hash" if prefix.nil?
    "#{escape(prefix)}=#{escape(value)}"
  end
end

.build_query(params) (mod_func)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 110

def build_query(params)
  params.map { |k, v|
    if v.class == Array
      build_query(v.map { |x| [k, x] })
    else
      v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}"
    end
  }.join("&")
end

.byte_ranges(env, size) (mod_func)

Parses the “Range:” header, if present, into an array of Range objects. Returns nil if the header is missing or syntactically invalid. Returns an empty array if none of the ranges are satisfiable.

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 402

def byte_ranges(env, size)
  get_byte_ranges env['HTTP_RANGE'], size
end

.clean_path_info(path_info) (mod_func)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 600

def clean_path_info(path_info)
  parts = path_info.split PATH_SEPS

  clean = []

  parts.each do |part|
    next if part.empty? || part == '.'
    part == '..' ? clean.pop : clean << part
  end

  clean_path = clean.join(::File::SEPARATOR)
  clean_path.prepend("/") if parts.empty? || parts.first.empty?
  clean_path
end

.clock_time (mod_func)

:nocov:

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 96

def clock_time
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
end

.escape(s) (mod_func)

URI escapes. (CGI style space to +)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 40

def escape(s)
  URI.encode_www_form_component(s)
end

.escape_html(string) (mod_func)

Escape ampersands, brackets and quotes to their HTML/XML entities.

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 190

def escape_html(string)
  CGI.escapeHTML(string.to_s)
end

.escape_path(s) (mod_func)

Like URI escaping, but with %20 instead of +. Strictly speaking this is true URI escaping.

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 46

def escape_path(s)
  URI_PARSER.escape s
end

.forwarded_values(forwarded_header) (mod_func)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 149

def forwarded_values(forwarded_header)
  return nil unless forwarded_header
  forwarded_header = forwarded_header.to_s.gsub("\n", ";")

  forwarded_header.split(';').each_with_object({}) do |field, values|
    field.split(',').each do |pair|
      pair = pair.split('=').map(&:strip).join('=')
      return nil unless pair =~ /\A(by|for|host|proto)="?([^"]+)"?\Z/i
      (values[$1.downcase.to_sym] ||= []) << $2
    end
  end
end

.get_byte_ranges(http_range, size) (mod_func)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 406

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(/,[ \t]*/).each do |range_spec|
    return nil unless range_spec.include?('-')
    range = range_spec.split('-')
    r0, r1 = range[0], range[1]
    if r0.nil? || r0.empty?
      return nil if r1.nil?
      # suffix-byte-range-spec, represents trailing suffix of file
      r0 = size - r1.to_i
      r0 = 0  if r0 < 0
      r1 = size - 1
    else
      r0 = r0.to_i
      if r1.nil?
        r1 = size - 1
      else
        r1 = r1.to_i
        return nil  if r1 < r0  # backwards range is syntactically invalid
        r1 = size - 1  if r1 >= size
      end
    end
    ranges << (r0..r1)  if r0 <= r1
  end

  return [] if ranges.map(&:size).sum > size

  ranges
end

.parse_cookies(env) ⇒ Hash (mod_func)

Parse cookies from the provided request environment using parse_cookies_header. Returns a map of cookie key to cookie value.

parse_cookies({'HTTP_COOKIE' => 'myname=myvalue'})
# => {'myname' => 'myvalue'}
[ GitHub ]

  
# File 'lib/rack/utils.rb', line 257

def parse_cookies(env)
  parse_cookies_header env[HTTP_COOKIE]
end

.parse_cookies_header(value) ⇒ Hash (mod_func)

Parse cookies from the provided header value according to RFC6265. The syntax for cookie headers only supports semicolons. Returns a map of cookie key to cookie value.

parse_cookies_header('myname=myvalue; max-age=0')
# => {"myname"=>"myvalue", "max-age"=>"0"}
[ GitHub ]

  
# File 'lib/rack/utils.rb', line 238

def parse_cookies_header(value)
  return {} unless value

  value.split(/; */n).each_with_object({}) do |cookie, cookies|
    next if cookie.empty?
    key, value = cookie.split('=', 2)
    cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
  end
end

.parse_nested_query(qs, d = nil) (mod_func)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 106

def parse_nested_query(qs, d = nil)
  Rack::Utils.default_query_parser.parse_nested_query(qs, d)
end

.parse_query(qs, d = nil, &unescaper) (mod_func)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 102

def parse_query(qs, d = nil, &unescaper)
  Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
end

.q_values(q_value_header) (mod_func)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 138

def q_values(q_value_header)
  q_value_header.to_s.split(',').map do |part|
    value, parameters = part.split(';', 2).map(&:strip)
    quality = 1.0
    if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
      quality = md[1].to_f
    end
    [value, quality]
  end
end

.rfc2822(time) (mod_func)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 395

def rfc2822(time)
  time.rfc2822
end

.secure_compare(a, b) (mod_func)

Constant time string comparison.

NOTE: the values compared should be of fixed length, such as strings that have already been processed by HMAC. This should not be used on variable length plaintext strings because it could leak length info via timing attacks.

See additional method definition at line 448.

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 455

def secure_compare(a, b)
  return false unless a.bytesize == b.bytesize

  OpenSSL.fixed_length_secure_compare(a, b)
end

.select_best_encoding(available_encodings, accept_encoding) (mod_func)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 196

def select_best_encoding(available_encodings, accept_encoding)
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

  expanded_accept_encoding = []

  accept_encoding.each do |m, q|
    preference = available_encodings.index(m) || available_encodings.size

    if m == "*"
      (available_encodings - accept_encoding.map(&:first)).each do |m2|
        expanded_accept_encoding << [m2, q, preference]
      end
    else
      expanded_accept_encoding << [m, q, preference]
    end
  end

  encoding_candidates = expanded_accept_encoding
    .sort_by { |_, q, p| [-q, p] }
    .map!(&:first)

  unless encoding_candidates.include?("identity")
    encoding_candidates.push("identity")
  end

  expanded_accept_encoding.each do |m, q|
    encoding_candidates.delete(m) if q == 0.0
  end

  (encoding_candidates & available_encodings)[0]
end

.status_code(status) (mod_func)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 582

def status_code(status)
  if status.is_a?(Symbol)
    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: 3
      fallback_code
    end
  else
    status.to_i
  end
end

.unescape(s, encoding = Encoding::UTF_8) (mod_func)

Unescapes a URI escaped string with encoding. encoding will be the target encoding of the string returned, and it defaults to UTF-8

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 58

def unescape(s, encoding = Encoding::UTF_8)
  URI.decode_www_form_component(s, encoding)
end

.unescape_path(s) (mod_func)

Unescapes the path component of a URI. See .unescape for unescaping query parameters or form components.

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 52

def unescape_path(s)
  URI_PARSER.unescape s
end

.valid_path?(path) ⇒ Boolean (mod_func)

[ GitHub ]

  
# File 'lib/rack/utils.rb', line 617

def valid_path?(path)
  path.valid_encoding? && !path.include?(NULL_BYTE)
end