require 'cgi' module Faraday module Utils extend self # Adapted from Rack::Utils::HeaderHash class Headers < ::Hash def initialize(hash={}) super() @names = {} self.update hash end # symbol -> string mapper + cache KeyMap = Hash.new do |map, key| map[key] = if key.respond_to?(:to_str) then key else key.to_s.split('_'). # :user_agent => %w(user agent) each { |w| w.capitalize! }. # => %w(User Agent) join('-') # => "User-Agent" end end KeyMap[:etag] = "ETag" def [](k) k = KeyMap[k] super(k) || super(@names[k.downcase]) end def []=(k, v) k = KeyMap[k] k = (@names[k.downcase] ||= k) # join multiple values with a comma v = v.to_ary.join(', ') if v.respond_to? :to_ary super k, v end def delete(k) k = KeyMap[k] if k = @names[k.downcase] @names.delete k.downcase super k end end def include?(k) @names.include? k.downcase end alias_method :has_key?, :include? alias_method :member?, :include? alias_method :key?, :include? def merge!(other) other.each { |k, v| self[k] = v } self end alias_method :update, :merge! def merge(other) hash = dup hash.merge! other end def replace(other) clear self.update other self end def to_hash() ::Hash.new.update(self) end def parse(header_string) return unless header_string && !header_string.empty? header_string.split(/\r\n/). tap { |a| a.shift if a.first.index('HTTP/') == 0 }. # drop the HTTP status line map { |h| h.split(/:\s+/, 2) }.reject { |p| p[0].nil? }. # split key and value, ignore blank lines each { |key, value| # join multiple values with a comma if self[key] then self[key] << ', ' << value else self[key] = value end } end end # hash with stringified keys class ParamsHash < Hash def [](key) super(convert_key(key)) end def []=(key, value) super(convert_key(key), value) end def delete(key) super(convert_key(key)) end def include?(key) super(convert_key(key)) end alias_method :has_key?, :include? alias_method :member?, :include? alias_method :key?, :include? def update(params) params.each do |key, value| self[key] = value end self end alias_method :merge!, :update def merge(params) dup.update(params) end def replace(other) clear update(other) end def merge_query(query) if query && !query.empty? update Utils.parse_query(query) end self end def to_query Utils.build_nested_query(self) end private def convert_key(key) key.to_s end end # Copied from Rack 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 # Rack's version modified to handle non-String values def build_nested_query(value, prefix = nil) case value when Array value.map { |v| build_nested_query(v, "#{prefix}%5B%5D") }.join("&") when Hash value.map { |k, v| build_nested_query(v, prefix ? "#{prefix}%5B#{escape(k)}%5D" : escape(k)) }.join("&") when NilClass prefix else raise ArgumentError, "value must be a Hash" if prefix.nil? "#{prefix}=#{escape(value)}" end end ESCAPE_RE = /[^\w .~-]+/ def escape(s) s.to_s.gsub(ESCAPE_RE) { '%' + $&.unpack('H2' * $&.bytesize).join('%').upcase }.tr(' ', '+') end def unescape(s) CGI.unescape s.to_s end DEFAULT_SEP = /[&;] */n # Adapted from Rack def parse_query(qs) params = {} (qs || '').split(DEFAULT_SEP).each do |p| k, v = p.split('=', 2).map { |x| unescape(x) } if cur = params[k] if cur.class == Array then params[k] << v else params[k] = [cur, v] end else params[k] = v end end params end def parse_nested_query(qs) params = {} (qs || '').split(DEFAULT_SEP).each do |p| k, v = p.split('=', 2).map { |s| unescape(s) } normalize_params(params, k, v) end params end # Stolen from Rack def normalize_params(params, name, v = nil) name =~ %r(\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 =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$) 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[k].last.is_a?(Hash) && !params[k].last.key?(child_key) normalize_params(params[k].last, child_key, v) else params[k] << normalize_params({}, child_key, v) end else params[k] ||= {} raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash) params[k] = normalize_params(params[k], after, v) end return params end # Receives a URL and returns just the path with the query string sorted. def normalize_path(url) (url.path != "" ? url.path : "/") + (url.query ? "?#{sort_query_params(url.query)}" : "") end # Recursive hash update def deep_merge!(target, hash) hash.each do |key, value| if Hash === value and Hash === target[key] target[key] = deep_merge(target[key], value) else target[key] = value end end target end # Recursive hash merge def deep_merge(source, hash) deep_merge!(source.dup, hash) end protected def sort_query_params(query) query.split('&').sort.join('&') end end end