module RestClient::Utils
Various utility methods
Public Class Methods
Parse semi-colon separated, potentially quoted header string iteratively.
@private
@deprecated This method is deprecated and only exists to support Ruby
2.0, which is not supported by HTTP::Accept.
@todo remove this method when dropping support for Ruby 2.0
# File lib/restclient/utils.rb, line 75 def self._cgi_parseparam(s) return enum_for(__method__, s) unless block_given? while s[0] == ';' s = s[1..-1] ends = s.index(';') while ends && ends > 0 \ && (s[0...ends].count('"') - s[0...ends].scan('\"').count) % 2 != 0 ends = s.index(';', ends + 1) end if ends.nil? ends = s.length end f = s[0...ends] yield f.strip s = s[ends..-1] end nil end
Parse a Content-Type like header.
Return the main content-type and a hash of params.
@param [String] line @return [Array(String, Hash)]
# File lib/restclient/utils.rb, line 56 def self.cgi_parse_header(line) types = HTTP::Accept::MediaTypes.parse(line) if types.empty? raise HTTP::Accept::ParseError.new("Found no types in header line") end [types.first.mime_type, types.first.parameters] end
Parse a Content-Type like header.
Return the main content-type and a hash of options.
This method was ported directly from Python's cgi.parse_header(). It probably doesn't read or perform particularly well in ruby. github.com/python/cpython/blob/3.4/Lib/cgi.py#L301-L331
@param [String] line @return [Array(String, Hash)]
@deprecated This method is deprecated and only exists to support Ruby
2.0, which is not supported by HTTP::Accept.
@todo remove this method when dropping support for Ruby 2.0
# File lib/restclient/utils.rb, line 112 def self.deprecated_cgi_parse_header(line) parts = _cgi_parseparam(';' + line) key = parts.next pdict = {} begin while (p = parts.next) i = p.index('=') if i name = p[0...i].strip.downcase value = p[i+1..-1].strip if value.length >= 2 && value[0] == '"' && value[-1] == '"' value = value[1...-1] value = value.gsub('\\\\', '\\').gsub('\\"', '"') end pdict[name] = value end end rescue StopIteration end [key, pdict] end
Serialize a ruby object into HTTP query string parameters.
There is no standard for doing this, so we choose our own slightly idiosyncratic format. The output closely matches the format understood by Rails, Rack, and PHP.
If you don't want handling of complex objects and only want to handle simple flat hashes, you may want to use `URI.encode_www_form` instead, which implements HTML5-compliant URL encoded form data.
@param [Hash,ParamsArray] object The object to serialize
@return [String] A string appropriate for use as an HTTP query string
@see {flatten_params}
@see URI.encode_www_form
@see See also Object#to_query in ActiveSupport @see php.net/manual/en/function.http-build-query.php
http_build_query in PHP
@see See also Rack::Utils.build_nested_query in Rack
Notable differences from the ActiveSupport implementation:
-
Empty hash and empty array are treated the same as nil instead of being omitted entirely from the output. Rather than disappearing, they will appear to be nil instead.
It's most common to pass a Hash as the object to serialize, but you can also use a ParamsArray
if you want to be able to pass the same key with multiple values and not use the rack/rails array convention.
@since 2.0.0
@example Simple hashes
>> encode_query_string({foo: 123, bar: 456}) => 'foo=123&bar=456'
@example Simple arrays
>> encode_query_string({foo: [1,2,3]}) => 'foo[]=1&foo[]=2&foo[]=3'
@example Nested hashes
>> encode_query_string({outer: {foo: 123, bar: 456}}) => 'outer[foo]=123&outer[bar]=456'
@example Deeply nesting
>> encode_query_string({coords: [{x: 1, y: 0}, {x: 2}, {x: 3}]}) => 'coords[][x]=1&coords[][y]=0&coords[][x]=2&coords[][x]=3'
@example Null and empty values
>> encode_query_string({string: '', empty: nil, list: [], hash: {}}) => 'string=&empty&list&hash'
@example Nested nulls
>> encode_query_string({foo: {string: '', empty: nil}}) => 'foo[string]=&foo[empty]'
@example Multiple fields with the same name using ParamsArray
>> encode_query_string(RestClient::ParamsArray.new([[:foo, 1], [:foo, 2], [:foo, 3]])) => 'foo=1&foo=2&foo=3'
@example Nested ParamsArray
>> encode_query_string({foo: RestClient::ParamsArray.new([[:a, 1], [:a, 2]])}) => 'foo[a]=1&foo[a]=2' >> encode_query_string(RestClient::ParamsArray.new([[:foo, {a: 1}], [:foo, {a: 2}]])) => 'foo[a]=1&foo[a]=2'
# File lib/restclient/utils.rb, line 206 def self.encode_query_string(object) flatten_params(object, true).map {|k, v| v.nil? ? k : "#{k}=#{v}" }.join('&') end
Encode string for safe transport by URI or form encoding. This uses a CGI style escape, which transforms ` ` into `+` and various special characters into percent encoded forms.
This calls URI.encode_www_form_component for the implementation. The only difference between this and CGI.escape is that it does not escape `*`. stackoverflow.com/questions/25085992/
@see URI.encode_www_form_component
# File lib/restclient/utils.rb, line 270 def self.escape(string) URI.encode_www_form_component(string) end
Transform deeply nested param containers into a flat array of [key, value] pairs.
@example
>> flatten_params({key1: {key2: 123}}) => [["key1[key2]", 123]]
@example
>> flatten_params({key1: {key2: 123, arr: [1,2,3]}}) => [["key1[key2]", 123], ["key1[arr][]", 1], ["key1[arr][]", 2], ["key1[arr][]", 3]]
@param object [Hash, ParamsArray] The container to flatten @param uri_escape [Boolean] Whether to URI escape keys and values @param parent_key [String] Should not be passed (used for recursion)
# File lib/restclient/utils.rb, line 225 def self.flatten_params(object, uri_escape=false, parent_key=nil) unless object.is_a?(Hash) || object.is_a?(ParamsArray) || (parent_key && object.is_a?(Array)) raise ArgumentError.new('expected Hash or ParamsArray, got: ' + object.inspect) end # transform empty collections into nil, where possible if object.empty? && parent_key return [[parent_key, nil]] end # This is essentially .map(), but we need to do += for nested containers object.reduce([]) { |result, item| if object.is_a?(Array) # item is already the value k = nil v = item else # item is a key, value pair k, v = item k = escape(k.to_s) if uri_escape end processed_key = parent_key ? "#{parent_key}[#{k}]" : k case v when Array, Hash, ParamsArray result.concat flatten_params(v, uri_escape, processed_key) else v = escape(v.to_s) if uri_escape && v result << [processed_key, v] end } end
Return encoding from an HTTP header hash.
We use the RFC 7231 specification and do not impose a default encoding on text. This differs from the older RFC 2616 behavior, which specifies using ISO-8859-1 for text/* content types without a charset.
Strings will use the default encoding when this method returns nil. This default is likely to be UTF-8 for Ruby >= 2.0
@param headers [Hash<Symbol,String>]
@return [String, nil] Return the string encoding or nil if no header is
found.
@example
>> get_encoding_from_headers({:content_type => 'text/plain; charset=UTF-8'}) => "UTF-8"
# File lib/restclient/utils.rb, line 25 def self.get_encoding_from_headers(headers) type_header = headers[:content_type] return nil unless type_header # TODO: remove this hack once we drop support for Ruby 2.0 if RUBY_VERSION.start_with?('2.0') _content_type, params = deprecated_cgi_parse_header(type_header) if params.include?('charset') return params.fetch('charset').gsub(/(\A["']*)|(["']*\z)/, '') end else begin _content_type, params = cgi_parse_header(type_header) rescue HTTP::Accept::ParseError return nil else params['charset'] end end end