require 'thread' Faraday.require_libs 'parameters' module Faraday module Utils extend self # Adapted from Rack::Utils::HeaderHash class Headers < ::Hash def self.from(value) new(value) end def initialize(hash = nil) super() @names = {} self.update(hash || {}) end # need to synchronize concurrent writes to the shared KeyMap keymap_mutex = Mutex.new # symbol -> string mapper + cache KeyMap = Hash.new do |map, key| value = if key.respond_to?(:to_str) key else key.to_s.split('_'). # :user_agent => %w(user agent) each { |w| w.capitalize! }. # => %w(User Agent) join('-') # => "User-Agent" end keymap_mutex.synchronize { map[key] = value } 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 fetch(k, *args, &block) k = KeyMap[k] key = @names.fetch(k.downcase, k) super(key, *args, &block) 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] 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, encoder = nil) if query && !query.empty? update((encoder || Utils.default_params_encoder).decode(query)) end self end def to_query(encoder = nil) (encoder || Utils.default_params_encoder).encode(self) end private def convert_key(key) key.to_s end end def build_query(params) FlatParamsEncoder.encode(params) end def build_nested_query(params) NestedParamsEncoder.encode(params) end ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/ def escape(s) s.to_s.gsub(ESCAPE_RE) {|match| '%' + match.unpack('H2' * match.bytesize).join('%').upcase }.tr(' ', '+') end def unescape(s) CGI.unescape s.to_s end DEFAULT_SEP = /[&;] */n # Adapted from Rack def parse_query(query) FlatParamsEncoder.decode(query) end def parse_nested_query(query) NestedParamsEncoder.decode(query) end def default_params_encoder @default_params_encoder ||= NestedParamsEncoder end class << self attr_writer :default_params_encoder end # Stolen from Rack def normalize_params(params, name, v = nil) name =~ %r(\A[\[\]]*([^\[\]]+)\]*) k = $1 || '' after = $' || '' return if k.empty? if after == "" if params[k] params[k] = Array[params[k]] unless params[k].kind_of?(Array) params[k] << v else params[k] = v end 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 # Normalize URI() behavior across Ruby versions # # url - A String or URI. # # Returns a parsed URI. def URI(url) if url.respond_to?(:host) url elsif url.respond_to?(:to_str) default_uri_parser.call(url) else raise ArgumentError, "bad argument (expected URI object or URI string)" end end def default_uri_parser @default_uri_parser ||= begin require 'uri' Kernel.method(:URI) end end def default_uri_parser=(parser) @default_uri_parser = if parser.respond_to?(:call) || parser.nil? parser else parser.method(:parse) end end # Receives a String or URI and returns just the path with the query string sorted. def normalize_path(url) url = URI(url) (url.path.start_with?('/') ? 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