# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. require 'aws/http/request' require 'aws/base_client' require 'aws/uri_escape' require 'uri' require 'time' module AWS class S3 # @private class Request < AWS::Http::Request include UriEscape # @param [bucket] S3 bucket name attr_accessor :bucket # @param [String] S3 object key attr_accessor :key attr_accessor :body_stream def metadata= metadata Array(metadata).each do |name, value| headers["x-amz-meta-#{name}"] = value end end def canned_acl= acl if acl.kind_of?(Symbol) headers["x-amz-acl"] = acl.to_s.gsub("_", "-") elsif acl headers["x-amz-acl"] = acl end end def storage_class= storage_class if storage_class.kind_of?(Symbol) headers["x-amz-storage-class"] = storage_class.to_s.upcase elsif storage_class headers["x-amz-storage-class"] = storage_class end end def host Client.path_style_bucket_name?(bucket) ? @host : "#{bucket}.#{@host}" end def path parts = [] parts << bucket if bucket and Client.path_style_bucket_name?(bucket) parts << escape_path(key) if key "/#{parts.join('/')}" end def querystring url_encoded_params end # @param [String, IO] The http request body. This can be a string or # any object that responds to #read and #eof? (like an IO object). def body= body @body_stream = StringIO.new(body) end # @return [String, nil] The http request body. def body if @body_stream string = @body_stream.read @body_stream.rewind string else nil end end # From the S3 developer guide: # # StringToSign = # HTTP-Verb + "\n" + # content-md5 + "\n" + # content-type + "\n" + # date + "\n" + # CanonicalizedAmzHeaders + CanonicalizedResource; # def string_to_sign [ http_method, headers.values_at('content-md5', 'content-type').join("\n"), signing_string_date, canonicalized_headers, canonicalized_resource, ].flatten.compact.join("\n") end def signing_string_date # if a date is provided via x-amz-date then we should omit the # Date header from the signing string (should appear as a blank line) if headers.detect{|k,v| k.to_s =~ /^x-amz-date$/i } '' else headers['date'] ||= Time.now.rfc822 end end # From the S3 developer guide # # CanonicalizedResource = # [ "/" + Bucket ] + # <HTTP-Request-URI, from the protocol name up to the querystring> + # [ sub-resource, if present. e.g. "?acl", "?location", # "?logging", or "?torrent"]; # def canonicalized_resource parts = [] # virtual hosted-style requests require the hostname to appear # in the canonicalized resource prefixed by a forward slash. if Client.dns_compatible_bucket_name?(bucket) and !Client.path_style_bucket_name?(bucket) then parts << "/#{bucket}" end # all requests require the portion of the un-decoded uri up to # but not including the query string parts << path # lastly any sub resource querystring params need to be appened # in lexigraphical ordered joined by '&' and prefixed by '?' params = (sub_resource_params + query_parameters_for_signature) unless params.empty? parts << '?' parts << params.sort.collect{|p| p.to_s }.join('&') end parts.join end # CanonicalizedAmzHeaders # # See the developer guide for more information on how this element # is generated. # def canonicalized_headers x_amz = headers.select{|name, value| name.to_s =~ /^x-amz-/i } x_amz = x_amz.collect{|name, value| [name.downcase, value] } x_amz = x_amz.sort_by{|name, value| name } x_amz = x_amz.collect{|name, value| "#{name}:#{value}" }.join("\n") x_amz == '' ? nil : x_amz end def sub_resource_params params.select{|p| self.class.sub_resources.include?(p.name) } end def query_parameters_for_signature params.select { |p| self.class.query_parameters.include?(p.name) } end def add_authorization!(signer) if signer.respond_to?(:session_token) and token = signer.session_token headers["x-amz-security-token"] = token end signature = URI.escape(signer.sign(string_to_sign, 'sha1')) headers["authorization"] = "AWS #{signer.access_key_id}:#{signature}" end class << self def sub_resources %w(acl location logging notification partNumber policy requestPayment torrent uploadId uploads versionId versioning versions) end def query_parameters %w(response-content-type response-content-language response-expires response-cache-control response-content-disposition response-content-encoding) end end end end end