s3lib 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/Rakefile +24 -0
  2. data/VERSION.yml +5 -0
  3. data/bin/s3lib +15 -0
  4. data/bin/s3sh_as +15 -0
  5. data/github-test.rb +22 -0
  6. data/lib/acl.rb +134 -0
  7. data/lib/acl_access.rb +20 -0
  8. data/lib/acl_creating_a_grant_recipe.rb +95 -0
  9. data/lib/acl_reading_acl_recipe.rb +59 -0
  10. data/lib/acl_refreshing_cached_grants_recipe.rb +54 -0
  11. data/lib/bucket.rb +116 -0
  12. data/lib/bucket_before_refactoring.rb +120 -0
  13. data/lib/bucket_create.rb +39 -0
  14. data/lib/bucket_find.rb +41 -0
  15. data/lib/bucket_with_acl_mixin.rb +103 -0
  16. data/lib/error_handling.rb +12 -0
  17. data/lib/grant.rb +107 -0
  18. data/lib/grant_creating_a_grant_recipe.rb +103 -0
  19. data/lib/grant_reading_acl_recipe.rb +51 -0
  20. data/lib/object.rb +144 -0
  21. data/lib/object_from_bucket_test.rb +18 -0
  22. data/lib/object_take1.rb +150 -0
  23. data/lib/object_with_acl_mixin.rb +131 -0
  24. data/lib/put_with_curl_test.rb +39 -0
  25. data/lib/s3_authenticator.rb +155 -0
  26. data/lib/s3_authenticator_dev.rb +117 -0
  27. data/lib/s3_authenticator_dev_private.rb +40 -0
  28. data/lib/s3_errors.rb +58 -0
  29. data/lib/s3lib.rb +10 -0
  30. data/lib/s3lib_with_mixin.rb +11 -0
  31. data/lib/service.rb +24 -0
  32. data/lib/service_dev.rb +36 -0
  33. data/s3lib.gemspec +74 -0
  34. data/sample_usage.rb +45 -0
  35. data/test/acl_test.rb +89 -0
  36. data/test/amazon_headers_test.rb +87 -0
  37. data/test/canonical_resource_test.rb +53 -0
  38. data/test/canonical_string_tests.rb +73 -0
  39. data/test/first_test.rb +34 -0
  40. data/test/first_test_private.rb +55 -0
  41. data/test/full_test.rb +84 -0
  42. data/test/s3_authenticator_test.rb +291 -0
  43. metadata +109 -0
@@ -0,0 +1,150 @@
1
+ # object.rb
2
+ require 'rexml/document'
3
+
4
+ module S3Lib
5
+
6
+ class ObjectDoesNotExist < StandardError
7
+ end
8
+
9
+ class ObjectAccessForbidden < StandardError
10
+ end
11
+
12
+ class NoContentError < S3Lib::S3ResponseError
13
+ end
14
+
15
+ class S3Object
16
+
17
+ DEFAULT_CONTENT_TYPE = 'binary/octect-stream'
18
+
19
+ attr_reader :key, :bucket
20
+
21
+ # This is just an alias for S3Object.new
22
+ def self.find(bucket, key, options = {})
23
+ S3Object.new(bucket, key, options)
24
+ end
25
+
26
+ def self.create(bucket, key, value = "", options = {})
27
+ options.merge!({:body => value || "", 'content-type' => DEFAULT_CONTENT_TYPE})
28
+ begin
29
+ response = S3Lib.request(:put, S3Object.url(bucket, key), options)
30
+ rescue S3Lib::S3ResponseError => error
31
+ case error.amazon_error_type
32
+ when 'NoSuchBucket': raise S3Lib::BucketNotFoundError.new("The bucket '#{bucket}' does not exist.", error.io, error.s3requester)
33
+ when 'AccessDenied': raise S3Lib::NotYourBucketError.new("The bucket '#{bucket}' is owned by someone else.", error.io, error.s3requester)
34
+ when 'MissingContentLength': raise S3Lib::NoContentError.new("You must provide a value to put in the object.\nUsage: S3Lib::S3Object.create(bucket, key, value, options)", error.io, error.s3requester)
35
+ else # Re-raise the error if it's not one of the above
36
+ raise
37
+ end
38
+ end
39
+ response.status[0] == "200" ? S3Object.new(bucket, key) : false
40
+ end
41
+
42
+ # Delete an object given the object's bucket and key.
43
+ # No error will be raised if the object does not exist.
44
+ def self.delete(bucket, key, options = {})
45
+ begin
46
+ response = S3Lib.request(:delete, S3Object.url(bucket, key), options)
47
+ rescue S3Lib::S3ResponseError => error
48
+ case error.amazon_error_type
49
+ when 'NoSuchBucket': raise S3Lib::BucketNotFoundError.new("The bucket '#{bucket}' does not exist.", error.io, error.s3requester)
50
+ when 'NotSignedUp': raise S3Lib::NotYourBucketError.new("The bucket '#{bucket}' is owned by somebody else", error.io, error.s3requester)
51
+ else # Re-raise the error if it's not one of the above
52
+ raise
53
+ end
54
+ end
55
+ puts response.status
56
+ end
57
+
58
+ def delete
59
+ S3Object.delete(@bucket, @key, @options)
60
+ end
61
+
62
+ def self.value(bucket, key, options = {})
63
+ request = S3Object.object_request(:get, S3Object.url(bucket, key), options)
64
+ request.read
65
+ end
66
+
67
+ # bucket can be either a Bucket object or a string containing the bucket's name
68
+ def self.url(bucket, key)
69
+ bucket_name = bucket.respond_to?(:name) ? bucket.name : bucket
70
+ File.join(bucket_name, key)
71
+ end
72
+
73
+ # Both metadata and value are loaded lazily if options[:lazy_load] is true
74
+ # This is used by Bucket.find so you don't make a request for every object in the bucket
75
+ # The bucket can be either a bucket object or a string containing the bucket's name
76
+ # The key is a string.
77
+ def initialize(bucket, key, options = {})
78
+ options.merge!(:lazy_load => false)
79
+ bucket = Bucket.find(bucket) unless bucket.respond_to?(:name)
80
+ @bucket = bucket
81
+ @key = key
82
+ @options = options
83
+ get_metadata unless options.delete(:lazy_load)
84
+ end
85
+
86
+ def url
87
+ S3Object.url(@bucket.name, @key)
88
+ end
89
+
90
+ def metadata
91
+ @metadata || get_metadata
92
+ end
93
+
94
+ def value
95
+ @value || get_value
96
+ end
97
+
98
+ def value=(value)
99
+ S3Object.object_request(:put, value)
100
+ @value = value
101
+ refresh_metadata
102
+ end
103
+
104
+ def refresh
105
+ get_value
106
+ end
107
+
108
+ def refresh_metadata
109
+ get_metadata
110
+ end
111
+
112
+ def content_type
113
+ metadata["content-type"]
114
+ end
115
+
116
+ def etag
117
+ metadata["etag"]
118
+ end
119
+
120
+ private
121
+
122
+ def self.object_request(verb, url, options = {})
123
+ begin
124
+ S3Lib.request(verb, url, options)
125
+ rescue S3Lib::S3ResponseError => error
126
+ case error.amazon_error_type
127
+ when 'NoSuchBucket': raise S3Lib::BucketNotFoundError.new("The bucket '#{bucket}' does not exist.", error.io, error.s3requester)
128
+ when 'NotSignedUp': raise S3Lib::NotYourBucketError.new("The bucket '#{bucket}' is owned by somebody else", error.io, error.s3requester)
129
+ when 'AccessDenied': raise S3Lib::NotYourBucketError.new("The bucket '#{bucket}' is owned by someone else.", error.io, error.s3requester)
130
+ when 'MissingContentLength': raise S3Lib::NoContentError.new("You must provide a value to put in the object.\nUsage: S3Lib::S3Object.create(bucket, key, value, options)", error.io, error.s3requester)
131
+ else # Re-raise the error if it's not one of the above
132
+ raise
133
+ end
134
+ end
135
+ end
136
+
137
+ def get_metadata
138
+ request = S3Object.object_request(:head, url)
139
+ @metadata = request.meta
140
+ end
141
+
142
+ def get_value
143
+ request = S3Object.object_request(:get, url)
144
+ @metadata = request.meta
145
+ @value = request.read
146
+ end
147
+
148
+ end
149
+
150
+ end
@@ -0,0 +1,131 @@
1
+ # s3_object.rb
2
+
3
+ module S3Lib
4
+
5
+ class S3Object
6
+
7
+ DEFAULT_CONTENT_TYPE = 'binary/octect-stream'
8
+
9
+ attr_reader :key, :bucket
10
+
11
+ include S3Lib::AclAccess
12
+
13
+ # This is just an alias for S3Object.new
14
+ def self.find(bucket, key, options = {})
15
+ S3Object.new(bucket, key, options)
16
+ end
17
+
18
+ def self.create(bucket, key, value = "", options = {})
19
+ # translate from :access to 'x-amz-acl'
20
+ options['x-amz-acl'] = options.delete(:access) if options[:access]
21
+ options.merge!({:body => value || "", 'content-type' => DEFAULT_CONTENT_TYPE})
22
+ response = S3Object.object_request(:put, S3Object.url(bucket, key), options)
23
+ response.status[0] == "200" ? S3Object.new(bucket, key, options) : false
24
+ end
25
+
26
+ # Delete an object given the object's bucket and key.
27
+ # No error will be raised if the object does not exist.
28
+ def self.delete(bucket, key, options = {})
29
+ S3Object.object_request(:delete, S3Object.url(bucket, key), options)
30
+ end
31
+
32
+ def delete
33
+ S3Object.delete(@bucket, @key, @options)
34
+ end
35
+
36
+ def self.value(bucket, key, options = {})
37
+ request = S3Object.object_request(:get, S3Object.url(bucket, key), options)
38
+ request.read
39
+ end
40
+
41
+ # Both metadata and value are loaded lazily if options[:lazy_load] is true
42
+ # This is used by Bucket.find so you don't make a request for every object in the bucket
43
+ # The bucket can be either a bucket object or a string containing the bucket's name
44
+ # The key is a string.
45
+ def initialize(bucket, key, options = {})
46
+ bucket = Bucket.find(bucket) unless bucket.respond_to?(:name)
47
+ @bucket = bucket
48
+ @key = key
49
+ @options = options
50
+ get_metadata unless options[:lazy_load]
51
+ end
52
+
53
+ # bucket can be either a Bucket object or a string containing the bucket's name
54
+ def self.url(bucket, key)
55
+ bucket_name = bucket.respond_to?(:name) ? bucket.name : bucket
56
+ File.join(bucket_name, key)
57
+ end
58
+
59
+ def url
60
+ S3Object.url(@bucket.name, @key)
61
+ end
62
+
63
+ def metadata
64
+ @metadata || get_metadata
65
+ end
66
+
67
+ def value(params = {})
68
+ refresh if params[:refresh]
69
+ @value || get_value
70
+ end
71
+
72
+ def value=(value)
73
+ S3Object.create(@bucket, @key, value, @options)
74
+ @value = value
75
+ refresh_metadata
76
+ end
77
+
78
+ def refresh
79
+ get_value
80
+ end
81
+
82
+ def refresh_metadata
83
+ get_metadata
84
+ end
85
+
86
+ def content_type
87
+ metadata["content-type"]
88
+ end
89
+
90
+ # strip off the leading and trailing double-quotes
91
+ def etag
92
+ metadata["etag"].sub(/\A\"/,'').sub(/\"\Z/, '')
93
+ end
94
+
95
+ def length
96
+ metadata["content-length"].to_i
97
+ end
98
+
99
+ private
100
+
101
+ def self.object_request(verb, url, options = {})
102
+ begin
103
+ options.delete(:lazy_load)
104
+ response = S3Lib.request(verb, url, options)
105
+ rescue S3Lib::S3ResponseError => error
106
+ case error.amazon_error_type
107
+ when 'NoSuchBucket': raise S3Lib::BucketNotFoundError.new("The bucket '#{bucket}' does not exist.", error.io, error.s3requester)
108
+ when 'NotSignedUp': raise S3Lib::NotYourBucketError.new("The bucket '#{bucket}' is owned by somebody else", error.io, error.s3requester)
109
+ when 'AccessDenied': raise S3Lib::NotYourBucketError.new("The bucket '#{bucket}' is owned by someone else.", error.io, error.s3requester)
110
+ when 'MissingContentLength': raise S3Lib::NoContentError.new("You must provide a value to put in the object.\nUsage: S3Lib::S3Object.create(bucket, key, value, options)", error.io, error.s3requester)
111
+ else # Re-raise the error if it's not one of the above
112
+ raise
113
+ end
114
+ end
115
+ response
116
+ end
117
+
118
+ def get_metadata
119
+ request = S3Object.object_request(:head, url, @options)
120
+ @metadata = request.meta
121
+ end
122
+
123
+ def get_value
124
+ request = S3Object.object_request(:get, url, @options)
125
+ @metadata = request.meta
126
+ @value = request.read
127
+ end
128
+
129
+ end
130
+
131
+ end
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+ require 's3lib'
3
+
4
+ module S3Lib
5
+
6
+ class AuthenticatedRequest
7
+
8
+ def public_authorization_string
9
+ authorization_string
10
+ end
11
+
12
+ end
13
+
14
+ end
15
+
16
+ value = 'testing'
17
+ key = 'test.txt'
18
+ auth_string = nil
19
+ date = Time.now.httpdate
20
+ begin
21
+ S3Lib.request(:put, "spatten_test_bucket/#{key}", :body => value, 'date' => date)
22
+ rescue => e
23
+ puts e.response
24
+ puts "authorization string:"
25
+ puts e.s3requester.public_authorization_string
26
+ auth_string = e.s3requester.public_authorization_string
27
+ end
28
+
29
+ puts "date: #{date}"
30
+ puts "Auth String:"
31
+ puts auth_string
32
+
33
+ puts "doing curl"
34
+ puts `curl -X PUT -d body=#{value} -d 'Authorization=#{auth_string}' -d 'date=#{date}' http://s3.amazonaws.com/spatten_test_bucket/#{key}`
35
+ puts "end of curl"
36
+
37
+ obj = S3Lib::S3Object.find('spatten_test_bucket', key)
38
+ puts "Content type: #{obj.content_type}"
39
+
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'rest-open-uri'
4
+
5
+ require 'base64'
6
+ require 'digest/sha1'
7
+ require 'openssl'
8
+ require 'pp'
9
+
10
+ class Hash
11
+
12
+ def downcase_keys
13
+ res = {}
14
+ each do |key, value|
15
+ key = key.downcase if key.respond_to?(:downcase)
16
+ res[key] = value
17
+ end
18
+ res
19
+ end
20
+
21
+ def join_values(separator = ',')
22
+ res = {}
23
+ each do |key, value|
24
+ res[key] = value.respond_to?(:join) ? value.join(separator) : value
25
+ end
26
+ res
27
+ end
28
+
29
+ end
30
+
31
+ module S3Lib
32
+
33
+ def self.request(verb, request_path, headers = {})
34
+ begin
35
+ s3requester = AuthenticatedRequest.new()
36
+ req = s3requester.make_authenticated_request(verb, request_path, headers)
37
+ rescue OpenURI::HTTPError=> e
38
+ raise S3Lib::S3ResponseError.new(e.message, e.io, s3requester)
39
+ end
40
+ end
41
+
42
+ class AuthenticatedRequest
43
+
44
+ POSITIONAL_HEADERS = ['content-md5', 'content-type', 'date']
45
+ AMAZON_HEADER_PREFIX = 'x-amz-'
46
+ HOST = 's3.amazonaws.com'
47
+ BUCKET_LIST_PARAMS = [:max_keys, :prefix, :marker, :delimiter]
48
+ SUB_RESOURCE_TYPES = ['acl', 'torrent', 'logging']
49
+
50
+ def make_authenticated_request(verb, request_path, headers = {})
51
+ @verb = verb
52
+ @request_path = request_path.gsub(/^\//,'') # Strip off the leading '/'
53
+
54
+ @amazon_id = ENV['AMAZON_ACCESS_KEY_ID']
55
+ @amazon_secret = ENV['AMAZON_SECRET_ACCESS_KEY']
56
+
57
+ @headers = headers.downcase_keys.join_values
58
+ get_bucket_list_params
59
+ get_bucket_name
60
+ fix_date
61
+
62
+ req = open(uri_with_bucket_list_params, @headers.merge(:method => @verb, 'Authorization' => authorization_string))
63
+ end
64
+
65
+ def canonical_string
66
+ "#{@verb.to_s.upcase}\n#{canonicalized_headers}#{canonicalized_resource}"
67
+ end
68
+
69
+ def get_bucket_name
70
+ @bucket = ""
71
+ return unless @headers.has_key?('host')
72
+ @headers['host'] = @headers['host'].downcase
73
+ return if @headers['host'] == 's3.amazonaws.com'
74
+ if @headers['host'] =~ /^([^.]+)(:\d\d\d\d)?\.#{HOST}$/
75
+ @bucket = $1.gsub(/\/$/,'') + '/'
76
+ else
77
+ @bucket = @headers['host'].gsub(/(:\d\d\d\d)$/, '').gsub(/\/$/,'') + '/'
78
+ end
79
+ end
80
+
81
+ def fix_date
82
+ @headers['date'] ||= Time.now.httpdate
83
+ @headers.delete('date') if @headers.has_key?('x-amz-date')
84
+ end
85
+
86
+ def uri
87
+ host = @headers['host'] || HOST
88
+ "http://" + File.join(host, URI.escape(@request_path))
89
+ end
90
+
91
+ def get_bucket_list_params
92
+ @bucket_list_params = {}
93
+ @headers.each do |key, value|
94
+ @bucket_list_params[key] = @headers.delete(key) if BUCKET_LIST_PARAMS.include?(key)
95
+ end
96
+ end
97
+
98
+ def uri_with_bucket_list_params
99
+ return uri if @bucket_list_params.empty?
100
+ uri_with_params = uri
101
+ bucket_list_string = @bucket_list_params.collect {|key, value| "#{key.to_s.gsub('_', '-')}=#{value}"}.join('&')
102
+ uri_with_params.sub(/\/$/, '') # remove trailing slash
103
+ uri_with_params += '?' unless uri =~ /\?$/ # Add trailing ?
104
+ uri_with_params += bucket_list_string # add bucket list params
105
+ uri_with_params
106
+ end
107
+
108
+ def authorization_string
109
+ generator = OpenSSL::Digest::Digest.new('sha1')
110
+ encoded_canonical = Base64.encode64(OpenSSL::HMAC.digest(generator, @amazon_secret, canonical_string)).strip
111
+
112
+ "AWS #{@amazon_id}:#{encoded_canonical}"
113
+ end
114
+
115
+ def canonicalized_headers
116
+ canonicalized_positional_headers + canonicalized_amazon_headers
117
+ end
118
+
119
+ def canonicalized_positional_headers
120
+ POSITIONAL_HEADERS.collect do |header|
121
+ (@headers[header] || "") + "\n"
122
+ end.join
123
+ end
124
+
125
+ def canonicalized_amazon_headers
126
+
127
+ # select all headers that start with x-amz-
128
+ amazon_headers = @headers.select do |header, value|
129
+ header =~ /^x-amz-/
130
+ end
131
+
132
+ # Sort them alpabetically by key
133
+ amazon_headers = amazon_headers.sort do |a, b|
134
+ a[0] <=> b[0]
135
+ end
136
+
137
+ # Collect all of the amazon headers like this:
138
+ # {key}:{value}\n
139
+ # The value has to have any whitespace on the left stripped from it
140
+ # and any new-lines replaced by a single space.
141
+ # Finally, return the headers joined together as a single string and return it.
142
+ amazon_headers.collect do |header, value|
143
+ "#{header}:#{value.lstrip.gsub("\n"," ")}\n"
144
+ end.join
145
+ end
146
+
147
+ def canonicalized_resource
148
+ canonicalized_resource_string = "/"
149
+ canonicalized_resource_string += @bucket
150
+ canonicalized_resource_string += @request_path
151
+ canonicalized_resource_string
152
+ end
153
+
154
+ end
155
+ end
@@ -0,0 +1,117 @@
1
+ class Hash
2
+
3
+ def downcase_keys
4
+ res = {}
5
+ each do |key, value|
6
+ key = key.downcase if key.respond_to?(:downcase)
7
+ res[key] = value
8
+ end
9
+ res
10
+ end
11
+
12
+ def join_values(separator = ',')
13
+ res = {}
14
+ each do |key, value|
15
+ res[key] = value.respond_to?(:join) ? value.join(separator) : value
16
+ end
17
+ res
18
+ end
19
+
20
+ end
21
+
22
+ module S3Lib
23
+ require 'time'
24
+ require 'base64'
25
+ require 'digest/sha1'
26
+ require 'openssl'
27
+
28
+ def self.request(verb, request_path, headers = {})
29
+ s3requester = AuthenticatedRequest.new()
30
+ s3requester.make_authenticated_request(verb, request_path, headers)
31
+ end
32
+
33
+ class AuthenticatedRequest
34
+
35
+ attr_reader :headers
36
+ POSITIONAL_HEADERS = ['content-md5', 'content-type', 'date']
37
+ HOST = 's3.amazonaws.com'
38
+
39
+ def make_authenticated_request(verb, request_path, headers = {})
40
+ @verb = verb
41
+ @request_path = request_path.gsub(/^\//,'') # Strip off the leading '/'
42
+ @amazon_id = ENV['AMAZON_ACCESS_KEY_ID']
43
+ @amazon_secret = ENV['AMAZON_SECRET_ACCESS_KEY']
44
+ @headers = headers.downcase_keys.join_values
45
+ fix_date
46
+ get_bucket_name
47
+ end
48
+
49
+ def fix_date
50
+ @headers['date'] ||= Time.now.httpdate
51
+ @headers.delete('date') if @headers.has_key?('x-amz-date')
52
+ end
53
+
54
+ def canonical_string
55
+ "#{@verb.to_s.upcase}\n#{canonicalized_headers}#{canonicalized_resource}"
56
+ end
57
+
58
+ def canonicalized_headers
59
+ "#{canonicalized_positional_headers}#{canonicalized_amazon_headers}"
60
+ end
61
+
62
+ def canonicalized_positional_headers
63
+ POSITIONAL_HEADERS.collect do |header|
64
+ (@headers[header] || "") + "\n"
65
+ end.join
66
+ end
67
+
68
+ def canonicalized_amazon_headers
69
+
70
+ # select all headers that start with x-amz-
71
+ amazon_headers = @headers.select do |header, value|
72
+ header =~ /^x-amz-/
73
+ end
74
+
75
+ # Sort them alpabetically by key
76
+ amazon_headers = amazon_headers.sort do |a, b|
77
+ a[0] <=> b[0]
78
+ end
79
+
80
+ # Collect all of the amazon headers like this:
81
+ # {key}:{value}\n
82
+ # The value has to have any whitespace on the left stripped from it
83
+ # and any new-lines replaced by a single space.
84
+ # Finally, return the headers joined together as a single string and return it.
85
+ amazon_headers.collect do |header, value|
86
+ "#{header}:#{value.lstrip.gsub("\n"," ")}\n"
87
+ end.join
88
+ end
89
+
90
+ def canonicalized_resource
91
+ canonicalized_resource_string = "/"
92
+ canonicalized_resource_string += @bucket
93
+ canonicalized_resource_string += @request_path
94
+ canonicalized_resource_string
95
+ end
96
+
97
+ def get_bucket_name
98
+ @bucket = ""
99
+ return unless @headers.has_key?('host')
100
+ @headers['host'] = @headers['host'].downcase
101
+ return if @headers['host'] == 's3.amazonaws.com'
102
+ if @headers['host'] =~ /^([^.]+)(:\d\d\d\d)?\.#{HOST}$/ # Virtual hosting
103
+ @bucket = $1.gsub(/\/$/,'') + '/'
104
+ else
105
+ @bucket = @headers['host'].gsub(/(:\d\d\d\d)$/, '').gsub(/\/$/,'') + '/' # CNAME Virtual hosting
106
+ end
107
+ end
108
+
109
+ def authorization_string
110
+ generator = OpenSSL::Digest::Digest.new('sha1')
111
+ encoded_canonical = Base64.encode64(OpenSSL::HMAC.digest(generator, @amazon_secret, canonical_string)).strip
112
+
113
+ "AWS #{@amazon_id}:#{encoded_canonical}"
114
+ end
115
+
116
+ end
117
+ end
@@ -0,0 +1,40 @@
1
+ module S3Lib
2
+ require 'time'
3
+
4
+ def self.request(verb, request_path, headers = {})
5
+ s3requester = AuthenticatedRequest.new()
6
+ s3requester.make_authenticated_request(verb, request_path, headers)
7
+ end
8
+
9
+ class AuthenticatedRequest
10
+
11
+ POSITIONAL_HEADERS = ['content-md5', 'content-type', 'date']
12
+
13
+ def make_authenticated_request(verb, request_path, headers = {})
14
+ @verb = verb
15
+ @headers = headers
16
+ fix_date
17
+ end
18
+
19
+ private
20
+
21
+ def fix_date
22
+ @headers['date'] ||= Time.now.httpdate
23
+ end
24
+
25
+ def canonical_string
26
+ "#{@verb.to_s.upcase}\n#{canonicalized_headers}"
27
+ end
28
+
29
+ def canonicalized_headers
30
+ "#{canonicalized_positional_headers}"
31
+ end
32
+
33
+ def canonicalized_positional_headers
34
+ POSITIONAL_HEADERS.collect do |header|
35
+ (@headers[header] || "") + "\n"
36
+ end.join
37
+ end
38
+
39
+ end
40
+ end
data/lib/s3_errors.rb ADDED
@@ -0,0 +1,58 @@
1
+ module S3Lib
2
+
3
+
4
+ class S3ResponseError < StandardError
5
+ attr_reader :response, :amazon_error_type, :status, :s3requester, :io
6
+ def initialize(message, io, s3requester)
7
+ @io = io
8
+ # Get the response and status from the IO object
9
+ @io.rewind
10
+ @response = @io.read
11
+ @io.rewind
12
+ @status = io.status
13
+
14
+ # The Amazon Error type will always look like <Code>AmazonErrorType</Code>. Find it with a RegExp.
15
+ @response =~ /<Code>(.*)<\/Code>/
16
+ @amazon_error_type = $1
17
+
18
+ # Make the AuthenticatedRequest instance available as well
19
+ @s3requester = s3requester
20
+
21
+ # Call the standard Error initializer
22
+ # if you put '%s' in the message it will be replaced by the amazon_error_type
23
+ message += "\namazon error type: %s" unless message =~ /\%s/
24
+ super(message % @amazon_error_type)
25
+ end
26
+ end
27
+
28
+ # Bucket errors
29
+
30
+ class NotYourBucketError < S3Lib::S3ResponseError
31
+ end
32
+
33
+ class BucketNotFoundError < S3Lib::S3ResponseError
34
+ end
35
+
36
+ class BucketNotEmptyError < S3Lib::S3ResponseError
37
+ end
38
+
39
+ # Object errors
40
+
41
+ class ObjectDoesNotExist < StandardError
42
+ end
43
+
44
+ class ObjectAccessForbidden < StandardError
45
+ end
46
+
47
+ class NoContentError < S3Lib::S3ResponseError
48
+ end
49
+
50
+ # ACL errors
51
+ class MalformedACLError < S3Lib::S3ResponseError
52
+ end
53
+
54
+ # Grant errors
55
+ class BadGrantTypeError < StandardError
56
+ end
57
+
58
+ end
data/lib/s3lib.rb ADDED
@@ -0,0 +1,10 @@
1
+ require File.join(File.dirname(__FILE__), 's3_authenticator')
2
+ require File.join(File.dirname(__FILE__), 's3_errors')
3
+ require File.join(File.dirname(__FILE__), 'service')
4
+ require File.join(File.dirname(__FILE__), 'object')
5
+ require File.join(File.dirname(__FILE__), 'bucket')
6
+ require File.join(File.dirname(__FILE__), 'acl')
7
+ require File.join(File.dirname(__FILE__), 'grant')
8
+ require 'rexml/document'
9
+ require 'rubygems'
10
+ require 'builder'