Module: Rack::Utils

### Home Classes Methods

### In Files

- lib/rack.rb
- lib/rack/utils.rb

### Namespace

- MODULE Rack::Utils::Multipart
- MODULE Rack::Utils::OkJson
- CLASS Rack::Utils::Context
- CLASS Rack::Utils::HeaderHash
- CLASS Rack::Utils::InvalidParameterError
- CLASS Rack::Utils::KeySpaceConstrainedParams
- CLASS Rack::Utils::ParameterTypeError

### Methods

- ::best_q_match
- ::build_nested_query
- ::build_query
- ::byte_ranges
- ::bytesize
- ::clean_path_info
- ::delete_cookie_header!
- ::escape
- ::escape_html
- ::escape_path
- ::normalize_params
- ::params_hash_type?
- ::parse_nested_query
- ::parse_query
- ::q_values
- ::rfc2109
- ::rfc2822
- ::secure_compare
- ::select_best_encoding
- ::set_cookie_header!
- ::status_code
- ::unescape

### Files

- KNOWN-ISSUES

### Class/Module Index

Quicksearch

- Rack
- Rack::Auth
- Rack::Auth::AbstractHandler
- Rack::Auth::AbstractRequest
- Rack::Auth::Basic
- Rack::Auth::Basic::Request
- Rack::Auth::Digest
- Rack::Auth::Digest::MD5
- Rack::Auth::Digest::Nonce
- Rack::Auth::Digest::Params
- Rack::Auth::Digest::Request
- Rack::BodyProxy
- Rack::Builder
- Rack::Cascade
- Rack::Chunked
- Rack::Chunked::Body
- Rack::CommonLogger
- Rack::ConditionalGet
- Rack::Config
- Rack::ContentLength
- Rack::ContentType
- Rack::Deflater
- Rack::Deflater::DeflateStream
- Rack::Deflater::GzipStream
- Rack::Directory
- Rack::ETag
- Rack::File
- Rack::ForwardRequest
- Rack::Handler
- Rack::Handler::CGI
- Rack::Handler::EventedMongrel
- Rack::Handler::FastCGI
- Rack::Handler::LSWS
- Rack::Handler::Mongrel
- Rack::Handler::SCGI
- Rack::Handler::SwiftipliedMongrel
- Rack::Handler::Thin
- Rack::Handler::WEBrick
- Rack::Head
- Rack::Lint
- Rack::Lobster
- Rack::Lock
- Rack::Logger
- Rack::MethodOverride
- Rack::Mime
- Rack::MockRequest
- Rack::MockRequest::FatalWarner
- Rack::MockRequest::FatalWarning
- Rack::MockResponse
- Rack::Multipart
- Rack::Multipart::Generator
- Rack::Multipart::MultipartPartLimitError
- Rack::Multipart::Parser
- Rack::Multipart::UploadedFile
- Rack::NullLogger
- Rack::Recursive
- Rack::Reloader
- Rack::Reloader::Stat
- Rack::Request
- Rack::Response
- Rack::Response::Helpers
- Rack::RewindableInput
- Rack::RewindableInput::Tempfile
- Rack::Runtime
- Rack::Sendfile
- Rack::Server
- Rack::Server::Options
- Rack::Session
- Rack::Session::Abstract
- Rack::Session::Abstract::ID
- Rack::Session::Abstract::SessionHash
- Rack::Session::Cookie
- Rack::Session::Cookie::Base64
- Rack::Session::Cookie::Base64::JSON
- Rack::Session::Cookie::Base64::Marshal
- Rack::Session::Cookie::Base64::ZipJSON
- Rack::Session::Cookie::Identity
- Rack::Session::Memcache
- Rack::Session::Pool
- Rack::ShowExceptions
- Rack::ShowStatus
- Rack::Static
- Rack::TempfileReaper
- Rack::URLMap
- Rack::Utils
- Rack::Utils::Context
- Rack::Utils::HeaderHash
- Rack::Utils::InvalidParameterError
- Rack::Utils::KeySpaceConstrainedParams
- Rack::Utils::Multipart
- Rack::Utils::OkJson
- Rack::Utils::OkJson::Error
- Rack::Utils::OkJson::Utf8Error
- Rack::Utils::ParameterTypeError
- FCGI
- FCGI::Stream
- WEBrick
- WEBrick::HTTPResponse
- Object

No matching classes.

# Rack::Utils

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

### Constants

DEFAULT_SEP
ESCAPE_HTML
ESCAPE_HTML_PATTERN

  On 1.8, there is a kcode = 'u' bug that allows for XSS otherwise TODO doesn't apply to jruby, so a better condition above might be preferable?

HTTP_STATUS_CODES

  Every standard HTTP code mapped to the appropriate message. Generated with: curl -s 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,"'

Multipart
PATH_SEPS
STATUS_WITH_NO_ENTITY_BODY

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

SYMBOL_TO_STATUS_CODE

### Attributes

key_space_limit[RW]
multipart_part_limit[RW]
param_depth_limit[RW]

### Public Class Methods

best_q_match(q_value_header, available_mimes) click to toggle source

    # File lib/rack/utils.rb, line 216
    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 && matches.first
    end

build_nested_query(value, prefix = nil) click to toggle source

    # File lib/rack/utils.rb, line 185
    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}[#{escape(k)}]" : escape(k))
        }.reject(&:empty?).join('&')
      when nil
        prefix
      else
        raise ArgumentError, "value must be a Hash" if prefix.nil?
        "#{prefix}=#{escape(value)}"
      end
    end

build_query(params) click to toggle source

    # File lib/rack/utils.rb, line 174
    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) click to toggle source

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.

    # File lib/rack/utils.rb, line 401
    def byte_ranges(env, size)
      # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
      http_range = env['HTTP_RANGE']
      return nil unless http_range && http_range =~ /bytes=([^;]+)/
      ranges = []
      $1.split(/,\s*/).each do |range_spec|
        return nil  unless range_spec =~ /(\d*)-(\d*)/
        r0,r1 = $1, $2
        if r0.empty?
          return nil  if r1.empty?
          # 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.empty?
            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
      ranges
    end

bytesize(string) click to toggle source

    # File lib/rack/utils.rb, line 367
    def bytesize(string)
      string.bytesize
    end

clean_path_info(path_info) click to toggle source

    # File lib/rack/utils.rb, line 666
    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.unshift '/' if parts.empty? || parts.first.empty?

      ::File.join(*clean)
    end

delete_cookie_header!(header, key, value = {}) click to toggle source

    # File lib/rack/utils.rb, line 333
    def delete_cookie_header!(header, key, value = {})
      case header["Set-Cookie"]
      when nil, ''
        cookies = []
      when String
        cookies = header["Set-Cookie"].split("\n")
      when Array
        cookies = header["Set-Cookie"]
      end

      cookies.reject! { |cookie|
        if value[:domain]
          cookie =~ /\A#{escape(key)}=.*domain=#{value[:domain]}/
        elsif value[:path]
          cookie =~ /\A#{escape(key)}=.*path=#{value[:path]}/
        else
          cookie =~ /\A#{escape(key)}=/
        end
      }

      header["Set-Cookie"] = cookies.join("\n")

      set_cookie_header!(header, key,
                 {:value => '', :path => nil, :domain => nil,
                   :max_age => '0',
                   :expires => Time.at(0) }.merge(value))

      nil
    end

escape(s) click to toggle source

URI escapes. (CGI style space to +)

    # File lib/rack/utils.rb, line 34
    def escape(s)
      URI.encode_www_form_component(s)
    end

escape_html(string) click to toggle source

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

    # File lib/rack/utils.rb, line 247
    def escape_html(string)
      string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
    end

escape_path(s) click to toggle source

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

    # File lib/rack/utils.rb, line 41
    def escape_path(s)
      escape(s).gsub('+', '%20')
    end

normalize_params(params, name, v = nil, depth = Utils.param_depth_limit) click to toggle source

normalize_params recursively expands parameters into structural types. If the structural types represented by two different parameter names are in conflict, a ParameterTypeError is raised.

    # File lib/rack/utils.rb, line 133
    def normalize_params(params, name, v = nil, depth = Utils.param_depth_limit)
      raise RangeError if depth <= 0

      name =~ %(\A[\[\]]*([^\[\]]+)\]*)
      k = $1 || ''
      after = $' || ''

      return if k.empty?

      if after == ""
        params[k] = v
      elsif after == "["
        params[name] = v
      elsif after == "[]"
        params[k] ||= []
        raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
        params[k] << v
      elsif after =~ %(^\[\]\[([^\[\]]+)\]$) || after =~ %(^\[\](.+)$)
        child_key = $1
        params[k] ||= []
        raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
        if params_hash_type?(params[k].last) && !params[k].last.key?(child_key)
          normalize_params(params[k].last, child_key, v, depth - 1)
        else
          params[k] << normalize_params(params.class.new, child_key, v, depth - 1)
        end
      else
        params[k] ||= params.class.new
        raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
        params[k] = normalize_params(params[k], after, v, depth - 1)
      end

      return params
    end

params_hash_type?(obj) click to toggle source

    # File lib/rack/utils.rb, line 169
    def params_hash_type?(obj)
      obj.kind_of?(KeySpaceConstrainedParams) || obj.kind_of?(Hash)
    end

parse_nested_query(qs, d = nil) click to toggle source

parse_nested_query expands a query string into structural types. Supported types are Arrays, Hashes and basic value types. It is possible to supply query strings with parameters of conflicting types, in this case a ParameterTypeError is raised. Users are encouraged to return a 400 in this case.

    # File lib/rack/utils.rb, line 115
    def parse_nested_query(qs, d = nil)
      params = KeySpaceConstrainedParams.new

      (qs || '').split(d ? /[#{d}] */ : DEFAULT_SEP).each do |p|
        k, v = p.split('=', 2).map { |s| unescape(s) }

        normalize_params(params, k, v)
      end

      return params.to_params_hash
    rescue ArgumentError => e
      raise InvalidParameterError, e.message
    end

parse_query(qs, d = nil, &unescaper) click to toggle source

Stolen from Mongrel, with some small modifications: Parses a query string by breaking it up at the '&' and ';' characters. You can also use this to parse cookies by changing the characters used in the second parameter (which defaults to '&;').

    # File lib/rack/utils.rb, line 86
    def parse_query(qs, d = nil, &unescaper)
      unescaper ||= method(:unescape)

      params = KeySpaceConstrainedParams.new

      (qs || '').split(d ? /[#{d}] */ : DEFAULT_SEP).each do |p|
        next if p.empty?
        k, v = p.split('=', 2).map(&unescaper)

        if cur = params[k]
          if cur.class == Array
            params[k] << v
          else
            params[k] = [cur, v]
          end
        else
          params[k] = v
        end
      end

      return params.to_params_hash
    end

q_values(q_value_header) click to toggle source

    # File lib/rack/utils.rb, line 204
    def q_values(q_value_header)
      q_value_header.to_s.split(/\s*,\s*/).map do |part|
        value, parameters = part.split(/\s*;\s*/, 2)
        quality = 1.0
        if md = /\Aq=([\d.]+)/.match(parameters)
          quality = md[1].to_f
        end
        [value, quality]
      end
    end

rfc2109(time) click to toggle source

Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead of '% %b %Y'. It assumes that the time is in GMT to comply to the RFC 2109.

NOTE: I'm not sure the RFC says it requires GMT, but is ambiguous enough that I'm certain someone implemented only that option. Do not use %a and %b from Time.strptime, it would use localized names for weekday and month.

    # File lib/rack/utils.rb, line 391
    def rfc2109(time)
      wday = Time::RFC2822_DAY_NAME[time.wday]
      mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
      time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
    end

rfc2822(time) click to toggle source

    # File lib/rack/utils.rb, line 377
    def rfc2822(time)
      time.rfc2822
    end

secure_compare(a, b) click to toggle source

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.

    # File lib/rack/utils.rb, line 437
    def secure_compare(a, b)
      return false unless bytesize(a) == bytesize(b)

      l = a.unpack("C*")

      r, i = 0, -1
      b.each_byte { |v| r |= v ^ l[i+=1] }
      r == 0
    end

select_best_encoding(available_encodings, accept_encoding) click to toggle source

    # File lib/rack/utils.rb, line 252
    def select_best_encoding(available_encodings, accept_encoding)
      # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

      expanded_accept_encoding =
        accept_encoding.map { |m, q|
          if m == "*"
            (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
          else
            [[m, q]]
          end
        }.inject([]) { |mem, list|
          mem + list
        }

      encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }

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

      expanded_accept_encoding.each { |m, q|
        encoding_candidates.delete(m) if q == 0.0
      }

      return (encoding_candidates & available_encodings)[0]
    end

set_cookie_header!(header, key, value) click to toggle source

    # File lib/rack/utils.rb, line 280
    def set_cookie_header!(header, key, value)
      case value
      when Hash
        domain  = "; domain="  + value[:domain]       if value[:domain]
        path    = "; path="    + value[:path]         if value[:path]
        max_age = "; max-age=" + value[:max_age].to_s if value[:max_age]
        # There is an RFC mess in the area of date formatting for Cookies. Not
        # only are there contradicting RFCs and examples within RFC text, but
        # there are also numerous conflicting names of fields and partially
        # cross-applicable specifications.
        #
        # These are best described in RFC 2616 3.3.1. This RFC text also
        # specifies that RFC 822 as updated by RFC 1123 is preferred. That is a
        # fixed length format with space-date delimeted fields.
        #
        # See also RFC 1123 section 5.2.14.
        #
        # RFC 6265 also specifies "sane-cookie-date" as RFC 1123 date, defined
        # in RFC 2616 3.3.1. RFC 6265 also gives examples that clearly denote
        # the space delimited format. These formats are compliant with RFC 2822.
        #
        # For reference, all involved RFCs are:
        # RFC 822
        # RFC 1123
        # RFC 2109
        # RFC 2616
        # RFC 2822
        # RFC 2965
        # RFC 6265
        expires = "; expires=" +
          rfc2822(value[:expires].clone.gmtime) if value[:expires]
        secure = "; secure"  if value[:secure]
        httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
        value = value[:value]
      end
      value = [value] unless Array === value
      cookie = escape(key) + "=" +
        value.map { |v| escape v }.join("&") +
        "#{domain}#{path}#{max_age}#{expires}#{secure}#{httponly}"

      case header["Set-Cookie"]
      when nil, ''
        header["Set-Cookie"] = cookie
      when String
        header["Set-Cookie"] = [header["Set-Cookie"], cookie].join("\n")
      when Array
        header["Set-Cookie"] = (header["Set-Cookie"] + [cookie]).join("\n")
      end

      nil
    end

status_code(status) click to toggle source

    # File lib/rack/utils.rb, line 653
    def status_code(status)
      if status.is_a?(Symbol)
        SYMBOL_TO_STATUS_CODE[status] || 500
      else
        status.to_i
      end
    end

unescape(s, encoding = Encoding::UTF_8) click to toggle source

    # File lib/rack/utils.rb, line 49
    def unescape(s, encoding = Encoding::UTF_8)
      URI.decode_www_form_component(s, encoding)
    end

[Validate]

Generated with the Darkfish Rdoc Generator 2.