Module: S33r
- Includes:
- Net
- Included in:
- Client
- Defined in:
- lib/s33r/core.rb,
lib/s33r/sync.rb,
lib/s33r/client.rb,
lib/s33r/named_bucket.rb,
lib/s33r/bucket_listing.rb,
lib/s33r/s33r_exception.rb
Overview
Module to handle S3 operations which don’t require an internet connection, i.e. data validation and request-building operations; also holds all the constants relating to S3.
Parts of this code are heavily based on Amazon’s code. Here’s their license:
This software code is made available "AS IS" without warranties of any
kind. You may copy, display, modify and redistribute the software
code either by itself or as incorporated into your code; provided that
you do not remove any proprietary notices. Your use of this software
code is at your own risk and you waive any claim against Amazon
Digital Services, Inc. or its affiliates with respect to your use of
this software code. (c) 2006 Amazon Digital Services, Inc. or its
affiliates.
Defined Under Namespace
Modules: S33rException, Sync Classes: BucketListing, Client, NamedBucket, S3Object, S3User
Constant Summary collapse
- HOST =
's3.amazonaws.com'
- PORT =
443
- NON_SSL_PORT =
80
- METADATA_PREFIX =
'x-amz-meta-'
- DEFAULT_CHUNK_SIZE =
Size of each chunk (in bytes) to be sent per request when putting files.
1048576
- AWS_HEADER_PREFIX =
'x-amz-'
- AWS_AUTH_HEADER_VALUE =
"AWS %s:%s"
- INTERESTING_HEADERS =
['content-md5', 'content-type', 'date']
- REQUIRED_HEADERS =
Headers which must be included with every request to S3.
['Content-Type', 'Date']
- CANNED_ACLS =
['private', 'public-read', 'public-read-write', 'authenticated-read']
- METHOD_VERBS =
HTTP methods which S3 will respond to.
['GET', 'PUT', 'HEAD', 'POST', 'DELETE']
- BUCKET_LIST_MAX_MAX_KEYS =
Maximum number which can be passed in max-keys parameter when GETting bucket list.
1000
- DEFAULT_EXPIRY_SECS =
Default number of seconds an authenticated URL will last for (15 minutes).
60 * 15
Class Method Summary collapse
-
.keys_to_symbols(hsh) ⇒ Object
Turn keys in a hash hsh into symbols.
Instance Method Summary collapse
-
#add_default_headers(headers, options = {}) ⇒ Object
Build the headers required with every S3 request (Date and Content-Type); options hash can contain extra header settings, as follows: :date and :content_type are required headers, and set to defaults if not supplied.
-
#bucket_name_valid?(bucket_name) ⇒ Boolean
Ensure that a bucket_name is well-formed (no leading or trailing slash).
-
#canned_acl_header(canned_acl, headers = {}) ⇒ Object
Add a canned ACL setter header.
-
#generate_auth_header_value(method, path, headers, aws_access_key, aws_secret_access_key) ⇒ Object
Get the value for the AWS authentication header.
-
#generate_canonical_string(method, path, headers = {}, expires = nil) ⇒ Object
Build canonical string for signing; modified (slightly) from the Amazon sample code.
-
#generate_querystring(pairs = {}) ⇒ Object
Convert a hash of name/value pairs to querystring variables.
-
#generate_signature(aws_secret_access_key, str) ⇒ Object
Encode the given string with the aws_secret_access_key, by taking the hmac sha1 sum, and then base64 encoding it.
-
#guess_mime_type(file_name) ⇒ Object
Guess a file’s mime type.
-
#metadata_headers(headers, metadata = {}) ⇒ Object
Add metadata headers, correctly prefixing them first.
-
#s3_authenticated_url(aws_access_key, aws_secret_access_key, bucket_name, resource_key, expires) ⇒ Object
Generate a get-able URL for an S3 resource key which passes authentication in querystring.
-
#s3_public_url(bucket_name, resource_key) ⇒ Object
The public URL for this key (which only works if public-read ACL is set).
-
#url_join(*args) ⇒ Object
Build URLs from fragments.
Class Method Details
.keys_to_symbols(hsh) ⇒ Object
Turn keys in a hash hsh into symbols. Returns a hash with ‘symbolised’ keys.
215 216 217 218 219 220 |
# File 'lib/s33r/core.rb', line 215 def S33r.keys_to_symbols(hsh) symbolised = hsh.inject({}) do |symbolised, key_value| symbolised.merge({key_value[0].to_sym => key_value[1]}) end symbolised end |
Instance Method Details
#add_default_headers(headers, options = {}) ⇒ Object
Build the headers required with every S3 request (Date and Content-Type); options hash can contain extra header settings, as follows: :date and :content_type are required headers, and set to defaults if not supplied
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/s33r/core.rb', line 111 def add_default_headers(headers, ={}) # set the default headers required by AWS missing_headers = REQUIRED_HEADERS - headers.keys if missing_headers.include?('Content-Type') headers['Content-Type'] = [:content_type] || '' end if missing_headers.include?('Date') date = [:date] || Time.now headers['Date'] = date.httpdate end headers end |
#bucket_name_valid?(bucket_name) ⇒ Boolean
Ensure that a bucket_name is well-formed (no leading or trailing slash).
156 157 158 159 160 |
# File 'lib/s33r/core.rb', line 156 def bucket_name_valid?(bucket_name) if ('/' == bucket_name[0,1] || '/' == bucket_name[-1,1]) raise S33rException::MalformedBucketName, "Bucket name cannot have a leading or trailing slash" end end |
#canned_acl_header(canned_acl, headers = {}) ⇒ Object
Add a canned ACL setter header.
137 138 139 140 141 142 143 144 145 |
# File 'lib/s33r/core.rb', line 137 def canned_acl_header(canned_acl, headers={}) unless canned_acl.nil? unless CANNED_ACLS.include?(canned_acl) raise S33rException::UnsupportedCannedACL, "The canned ACL #{canned_acl} is not supported" end headers[AWS_HEADER_PREFIX + 'acl'] = canned_acl end headers end |
#generate_auth_header_value(method, path, headers, aws_access_key, aws_secret_access_key) ⇒ Object
Get the value for the AWS authentication header.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/s33r/core.rb', line 85 def generate_auth_header_value(method, path, headers, aws_access_key, aws_secret_access_key) raise S33rException::MethodNotAvailable, "Method %s not available" % method if !METHOD_VERBS.include?(method) # check the headers needed for authentication have been set missing_headers = REQUIRED_HEADERS - headers.keys if !(missing_headers.empty?) raise S33rException::MissingRequiredHeaders, "Headers required for AWS auth value are missing: " + missing_headers.join(', ') end # get the AWS header canonical_string = generate_canonical_string(method, path, headers) signature = generate_signature(aws_secret_access_key, canonical_string) AWS_AUTH_HEADER_VALUE % [aws_access_key, signature] end |
#generate_canonical_string(method, path, headers = {}, expires = nil) ⇒ Object
Build canonical string for signing; modified (slightly) from the Amazon sample code.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/s33r/core.rb', line 44 def generate_canonical_string(method, path, headers={}, expires=nil) interesting_headers = {} headers.each do |key, value| lk = key.downcase if (INTERESTING_HEADERS.include?(lk) or lk =~ /^#{AWS_HEADER_PREFIX}/o) interesting_headers[lk] = value end end # these fields get empty strings if they don't exist. interesting_headers['content-type'] ||= '' interesting_headers['content-md5'] ||= '' # if you're using expires for query string auth, then it trumps date if not expires.nil? interesting_headers['date'] = expires end buf = "#{method}\n" interesting_headers.sort { |a, b| a[0] <=> b[0] }.each do |key, value| if key =~ /^#{AWS_HEADER_PREFIX}/o buf << "#{key}:#{value}\n" else buf << "#{value}\n" end end # ignore everything after the question mark... buf << path.gsub(/\?.*$/, '') # ...unless there is an acl or torrent parameter if path =~ /[&?]acl($|&|=)/ buf << '?acl' elsif path =~ /[&?]torrent($|&|=)/ buf << '?torrent' end return buf end |
#generate_querystring(pairs = {}) ⇒ Object
Convert a hash of name/value pairs to querystring variables. Name for a variable can be a string or symbol.
164 165 166 167 168 169 170 |
# File 'lib/s33r/core.rb', line 164 def generate_querystring(pairs={}) str = '' if pairs.size > 0 str += "?" + pairs.map { |key, value| "#{key}=#{CGI::escape(value.to_s)}" }.join('&') end str end |
#generate_signature(aws_secret_access_key, str) ⇒ Object
Encode the given string with the aws_secret_access_key, by taking the hmac sha1 sum, and then base64 encoding it.
103 104 105 106 |
# File 'lib/s33r/core.rb', line 103 def generate_signature(aws_secret_access_key, str) digest = OpenSSL::HMAC::digest(OpenSSL::Digest::Digest.new("SHA1"), aws_secret_access_key, str) Base64.encode64(digest).strip end |
#guess_mime_type(file_name) ⇒ Object
Guess a file’s mime type. If the mime_type for a file cannot be guessed, “text/plain” is used.
149 150 151 152 153 |
# File 'lib/s33r/core.rb', line 149 def guess_mime_type(file_name) mime_type = MIME::Types.type_for(file_name)[0] mime_type ||= MIME::Types['text/plain'][0] mime_type end |
#metadata_headers(headers, metadata = {}) ⇒ Object
Add metadata headers, correctly prefixing them first. Returns headers with the metadata headers appended.
129 130 131 132 133 134 |
# File 'lib/s33r/core.rb', line 129 def (headers, ={}) unless .empty? .each { |key, value| headers[METADATA_PREFIX + key] = value } end headers end |
#s3_authenticated_url(aws_access_key, aws_secret_access_key, bucket_name, resource_key, expires) ⇒ Object
Generate a get-able URL for an S3 resource key which passes authentication in querystring. int expires: when the URL expires (seconds since the epoch)
200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/s33r/core.rb', line 200 def s3_authenticated_url(aws_access_key, aws_secret_access_key, bucket_name, resource_key, expires) path = '/' + bucket_name + '/' + resource_key canonical_string = generate_canonical_string('GET', path, {}, expires) signature = generate_signature(aws_secret_access_key, canonical_string) querystring = generate_querystring({ 'Signature' => signature, 'Expires' => expires, 'AWSAccessKeyId' => aws_access_key }) return s3_public_url(bucket_name, resource_key) + querystring end |
#s3_public_url(bucket_name, resource_key) ⇒ Object
The public URL for this key (which only works if public-read ACL is set).
194 195 196 |
# File 'lib/s33r/core.rb', line 194 def s3_public_url(bucket_name, resource_key) "http://" + HOST + '/' + bucket_name + '/' + resource_key end |
#url_join(*args) ⇒ Object
Build URLs from fragments. Does similar job to File.join but puts forward slash between arguments (only if it’s not already there).
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/s33r/core.rb', line 175 def url_join(*args) url_start = '' url_end = args.join('/') # string index where the scheme of the URL (xxxx://) ends scheme_ends_at = (url_end =~ /:\/\//) unless scheme_ends_at.nil? scheme_ends_at = scheme_ends_at + 1 url_start = url_end[0..scheme_ends_at] url_end = url_end[(scheme_ends_at + 1)..-1] end # replace any multiple forward slashes (except those in the scheme) url_end = url_end.gsub(/\/{2,}/, '/') url_start + url_end end |