diff --git a/lib/signature.rb b/lib/signature.rb index 6f76c77..a3087bf 100644 --- a/lib/signature.rb +++ b/lib/signature.rb @@ -1,5 +1,7 @@ require 'openssl' +require 'signature/query_encoder' + module Signature class AuthenticationError < RuntimeError; end @@ -18,6 +20,8 @@ def sign(request) class Request attr_accessor :path, :query_hash + include QueryEncoder + # http://www.w3.org/TR/NOTE-datetime ISO8601 = "%Y-%m-%dT%H:%M:%SZ" @@ -182,7 +186,7 @@ def parameter_string # Exclude signature from signature generation! hash.delete("auth_signature") - hash.keys.sort.map { |k| "#{k}=#{hash[k]}" }.join("&") + hash.sort.map { |k, v| QueryEncoder.encode_param(k, v) }.join('&') end def validate_version! diff --git a/lib/signature/query_encoder.rb b/lib/signature/query_encoder.rb new file mode 100644 index 0000000..93d104f --- /dev/null +++ b/lib/signature/query_encoder.rb @@ -0,0 +1,38 @@ +module Signature + # Query string encoding extracted with thanks from em-http-request + module QueryEncoder + class << self + # URL encodes query parameters: + # single k=v, or a URL encoded array, if v is an array of values + def encode_param(k, v) + if v.is_a?(Array) + v.map { |e| escape(k) + "[]=" + escape(e) }.join("&") + else + escape(k) + "=" + escape(v) + end + end + + private + + def escape(s) + if defined?(EscapeUtils) + EscapeUtils.escape_url(s.to_s) + else + s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/n) { + '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase + } + end + end + + if ''.respond_to?(:bytesize) + def bytesize(string) + string.bytesize + end + else + def bytesize(string) + string.size + end + end + end + end +end diff --git a/spec/signature_spec.rb b/spec/signature_spec.rb index 3aa7e5f..4390deb 100644 --- a/spec/signature_spec.rb +++ b/spec/signature_spec.rb @@ -60,6 +60,13 @@ @request.sign(@token)[:auth_signature].should == @signature end + it "should generate correct string when query hash contains array" do + @request.query_hash = { + "things" => ["thing1", "thing2"] + } + @request.send(:string_to_sign).should == "POST\n/some/path\nthings[]=thing1&things[]=thing2" + end + it "should use the path to generate signature" do @request.path = '/some/other/path' @request.sign(@token)[:auth_signature].should_not == @signature