lib/amazon_associate/request.rb in dpickett-amazon_associate-0.6.5 vs lib/amazon_associate/request.rb in dpickett-amazon_associate-0.7.0
- old
+ new
@@ -7,11 +7,11 @@
rescue LoadError
require 'digest/md5'
end
#--
-# Copyright (c) 2006 Herryanto Siatono, Pluit Solutions
+# Copyright (c) 2009 Dan Pickett, Enlight Solutions
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
@@ -31,16 +31,16 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
module AmazonAssociate
class Request
- SERVICE_URLS = {:us => "http://webservices.amazon.com/onca/xml?Service=AWSECommerceService",
- :uk => "http://webservices.amazon.co.uk/onca/xml?Service=AWSECommerceService",
- :ca => "http://webservices.amazon.ca/onca/xml?Service=AWSECommerceService",
- :de => "http://webservices.amazon.de/onca/xml?Service=AWSECommerceService",
- :jp => "http://webservices.amazon.co.jp/onca/xml?Service=AWSECommerceService",
- :fr => "http://webservices.amazon.fr/onca/xml?Service=AWSECommerceService"
+ SERVICE_URLS = {:us => "http://webservices.amazon.com",
+ :uk => "http://webservices.amazon.co.uk",
+ :ca => "http://webservices.amazon.ca",
+ :de => "http://webservices.amazon.de",
+ :jp => "http://webservices.amazon.co.jp",
+ :fr => "http://webservices.amazon.fr"
}
# The sort types available to each product search index.
SORT_TYPES = {
"Apparel" => %w[relevancerank salesrank pricerank inverseprice -launch-date sale-flag],
@@ -227,33 +227,42 @@
end
# Generic send request to ECS REST service. You have to specify the :operation parameter.
def self.send_request(opts)
opts = self.options.merge(opts) if self.options
- request_url = prepare_url(opts)
+ unsigned_url = prepare_unsigned_url(opts)
response = nil
if caching_enabled?
AmazonAssociate::CacheFactory.sweep(self.options[:caching_strategy])
- res = AmazonAssociate::CacheFactory.get(request_url, self.options[:caching_strategy])
- response = Response.new(res, request_url) unless res.nil?
+ res = AmazonAssociate::CacheFactory.get(unsigned_url, self.options[:caching_strategy])
+ response = Response.new(res, unsigned_url) unless res.nil?
end
if !caching_enabled? || response.nil?
+ request_url = prepare_signed_url(opts)
log "Request URL: #{request_url}"
res = Net::HTTP.get_response(URI::parse(request_url))
+
unless res.kind_of? Net::HTTPSuccess
raise AmazonAssociate::RequestError, "HTTP Response: #{res.code} #{res.message}"
end
+
response = Response.new(res.body, request_url)
- cache_response(request_url, response, self.options[:caching_strategy]) if caching_enabled?
+ response.unsigned_url = unsigned_url
+
+ if caching_enabled?
+ cache_response(unsigned_url, response, self.options[:caching_strategy])
+ end
end
response
end
+ attr_accessor :request_url, :unsigned_url
+
protected
def self.log(s)
return unless self.debug
if defined? RAILS_DEFAULT_LOGGER
RAILS_DEFAULT_LOGGER.error(s)
@@ -262,24 +271,73 @@
else
puts s
end
end
- private
- def self.prepare_url(opts)
+ private
+ def self.get_service_url(opts)
country = opts.delete(:country)
country = (country.nil?) ? "us" : country
- request_url = SERVICE_URLS[country.to_sym]
- raise AmazonAssociate::RequestError, "Invalid country \"#{country}\"" unless request_url
+ url = SERVICE_URLS[country.to_sym]
+
+ raise AmazonAssociate::RequestError, "Invalid country \"#{country}\"" unless url
+ url
+ end
+
+ def self.prepare_unsigned_url(opts)
+ url = get_service_url(opts) + "/onca/xml"
qs = ""
opts.each {|k,v|
next unless v
- next if [:caching_options, :caching_strategy].include?(k)
+ next if [:caching_options, :caching_strategy, :secret_key].include?(k)
v = v.join(",") if v.is_a? Array
qs << "&#{camelize(k.to_s)}=#{URI.encode(v.to_s)}"
}
- "#{request_url}#{qs}"
+
+ @unsigned_url = "#{url}#{qs}"
+ end
+
+ def self.prepare_signed_url(opts)
+ url = get_service_url(opts) + "/onca/xml"
+
+ unencoded_key_value_strings = []
+ encoded_key_value_strings = []
+ opts[:timestamp] = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S") + ".000Z"
+ opts[:service] = "AWSECommerceService"
+ opts[:version] = "2009-01-01"
+ sort_parameters(opts).each do |p|
+ next if p[1].nil?
+ next if [:caching_options, :caching_strategy, :secret_key].include?(p[0])
+
+
+ encoded_value = CGI.escape(p[1].to_s)
+
+ encoded_key_value_strings << camelize(p[0].to_s ) + "=" + encoded_value
+ end
+
+ string_to_sign =
+"GET
+#{get_service_url(opts).gsub("http://", "")}
+/onca/xml
+#{encoded_key_value_strings.join("&")}"
+
+ signature = sign_string(string_to_sign)
+ encoded_key_value_strings << "Signature=" + signature
+
+ "#{url}?#{encoded_key_value_strings.join("&")}"
+ end
+
+ def self.sort_parameters(opts)
+ key_value_strings = []
+ opts.sort {|a, b| camelize(a) <=> camelize(b) }
+ end
+
+ def self.sign_string(string_to_sign)
+ sha1 = HMAC::SHA256.digest(self.options[:secret_key], string_to_sign)
+
+ #Base64 encoding adds a linefeed to the end of the string so chop the last character!
+ CGI.escape(Base64.encode64(sha1).chomp)
end
def self.camelize(s)
s.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
end