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::KeySpaceConstrainedParams

### Methods

- ::best_q_match
- ::build_nested_query
- ::build_query
- ::byte_ranges
- ::bytesize
- ::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::MultipartLimitError
- 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::Identity
- Rack::Session::Cookie::Reverse
- Rack::Session::Memcache
- Rack::Session::Pool
- Rack::ShowExceptions
- Rack::ShowStatus
- Rack::Static
- Rack::URLMap
- Rack::Utils
- Rack::Utils::Context
- Rack::Utils::HeaderHash
- Rack::Utils::KeySpaceConstrainedParams
- Rack::Utils::Multipart
- Rack::Utils::OkJson
- Rack::Utils::OkJson::Error
- Rack::Utils::OkJson::Utf8Error
- FCGI
- FCGI::Stream
- 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 otherwhise 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: irb -ropen-uri -rnokogiri > Nokogiri::XML(open("www.iana.org/assignments/http-status-codes/http-status-codes.xml")).css("record").each{|r|

      puts "#{r.css('value').text} => '#{r.css('description').text}'"}

Multipart
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 194
    def best_q_match(q_value_header, available_mimes)
      values = q_values(q_value_header)

      values.map do |req_mime, quality|
        match = available_mimes.first { |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.first
    end

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

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

build_query(params) click to toggle source

    # File lib/rack/utils.rb, line 152
    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 380
    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 346
    def bytesize(string)
      string.bytesize
    end

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

    # File lib/rack/utils.rb, line 312
    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 25
    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 224
    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 32
    def escape_path(s)
      escape(s).gsub('+', '%20')
    end

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

    # File lib/rack/utils.rb, line 113
    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[k] ||= []
        raise TypeError, "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 TypeError, "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 TypeError, "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 147
    def params_hash_type?(obj)
      obj.kind_of?(KeySpaceConstrainedParams) || obj.kind_of?(Hash)
    end

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

    # File lib/rack/utils.rb, line 100
    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
    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 76
    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 182
    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 ambigous 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 370
    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 356
    def rfc2822(time)
      time.rfc2822
    end

secure_compare(a, b) click to toggle source

Constant time string comparison.

    # File lib/rack/utils.rb, line 411
    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 229
    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.find_all { |m, q|
        q == 0.0
      }.each { |m, _|
        encoding_candidates.delete(m)
      }

      return (encoding_candidates & available_encodings)[0]
    end

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

    # File lib/rack/utils.rb, line 259
    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] 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[:httponly]
        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 628
    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 40
    def unescape(s, encoding = Encoding::UTF_8)
      URI.decode_www_form_component(s, encoding)
    end

[Validate]

Generated with the Darkfish Rdoc Generator 2.