s33r 0.4.2 → 0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. data/examples/cli/instant_download_server.rb +88 -0
  2. data/examples/cli/s3cli.rb +31 -52
  3. data/examples/cli/simple.rb +16 -6
  4. data/examples/fores33r/app/controllers/browser_controller.rb +12 -10
  5. data/examples/fores33r/app/helpers/application_helper.rb +2 -1
  6. data/examples/fores33r/app/views/browser/_upload.rhtml +1 -1
  7. data/examples/fores33r/app/views/browser/index.rhtml +4 -4
  8. data/examples/fores33r/config/environment.rb +5 -3
  9. data/examples/fores33r/log/development.log +2259 -0
  10. data/examples/fores33r/log/mongrel.log +59 -0
  11. data/examples/s3.yaml +2 -6
  12. data/lib/s33r/bucket.rb +103 -0
  13. data/lib/s33r/bucket_listing.rb +33 -76
  14. data/lib/s33r/client.rb +305 -446
  15. data/lib/s33r/networking.rb +197 -0
  16. data/lib/s33r/s33r_exception.rb +29 -18
  17. data/lib/s33r/s33r_http.rb +36 -18
  18. data/lib/s33r/s3_acl.rb +32 -52
  19. data/lib/s33r/s3_logging.rb +117 -0
  20. data/lib/s33r/s3_obj.rb +124 -69
  21. data/lib/s33r/utility.rb +447 -0
  22. data/test/cases/spec_acl.rb +10 -40
  23. data/test/cases/spec_bucket_listing.rb +12 -32
  24. data/test/cases/spec_logging.rb +47 -0
  25. data/test/cases/spec_networking.rb +11 -0
  26. data/test/cases/spec_s3_object.rb +44 -5
  27. data/test/cases/spec_utility.rb +264 -0
  28. data/test/files/acl.xml +0 -6
  29. data/test/files/config.yaml +5 -0
  30. data/test/files/logging_status_disabled.xml +3 -0
  31. data/test/files/logging_status_enabled.xml +7 -0
  32. data/test/test_setup.rb +7 -2
  33. metadata +16 -94
  34. data/examples/cli/acl_x.rb +0 -41
  35. data/examples/cli/logging_x.rb +0 -20
  36. data/examples/fores33r/README +0 -183
  37. data/html/classes/MIME.html +0 -120
  38. data/html/classes/MIME/InvalidContentType.html +0 -119
  39. data/html/classes/MIME/Type.html +0 -1173
  40. data/html/classes/MIME/Types.html +0 -566
  41. data/html/classes/Net.html +0 -108
  42. data/html/classes/Net/HTTPGenericRequest.html +0 -233
  43. data/html/classes/Net/HTTPResponse.html +0 -271
  44. data/html/classes/S33r.html +0 -986
  45. data/html/classes/S33r/BucketListing.html +0 -434
  46. data/html/classes/S33r/Client.html +0 -1575
  47. data/html/classes/S33r/LoggingResource.html +0 -222
  48. data/html/classes/S33r/NamedBucket.html +0 -693
  49. data/html/classes/S33r/OrderlyXmlMarkup.html +0 -165
  50. data/html/classes/S33r/S33rException.html +0 -124
  51. data/html/classes/S33r/S33rException/BucketListingMaxKeysError.html +0 -111
  52. data/html/classes/S33r/S33rException/BucketNotLogTargetable.html +0 -119
  53. data/html/classes/S33r/S33rException/InvalidBucketListing.html +0 -111
  54. data/html/classes/S33r/S33rException/InvalidPermission.html +0 -111
  55. data/html/classes/S33r/S33rException/InvalidS3GroupType.html +0 -111
  56. data/html/classes/S33r/S33rException/MalformedBucketName.html +0 -111
  57. data/html/classes/S33r/S33rException/MethodNotAvailable.html +0 -111
  58. data/html/classes/S33r/S33rException/MissingBucketName.html +0 -111
  59. data/html/classes/S33r/S33rException/MissingRequiredHeaders.html +0 -111
  60. data/html/classes/S33r/S33rException/MissingResource.html +0 -111
  61. data/html/classes/S33r/S33rException/S3FallenOver.html +0 -111
  62. data/html/classes/S33r/S33rException/TryingToPutEmptyResource.html +0 -117
  63. data/html/classes/S33r/S33rException/UnsupportedCannedACL.html +0 -111
  64. data/html/classes/S33r/S33rException/UnsupportedHTTPMethod.html +0 -111
  65. data/html/classes/S33r/S3ACL.html +0 -125
  66. data/html/classes/S33r/S3ACL/ACLDoc.html +0 -521
  67. data/html/classes/S33r/S3ACL/AmazonCustomer.html +0 -168
  68. data/html/classes/S33r/S3ACL/CanonicalUser.html +0 -212
  69. data/html/classes/S33r/S3ACL/Grant.html +0 -403
  70. data/html/classes/S33r/S3ACL/Grantee.html +0 -239
  71. data/html/classes/S33r/S3ACL/Group.html +0 -178
  72. data/html/classes/S33r/S3Object.html +0 -618
  73. data/html/classes/S33r/Sync.html +0 -152
  74. data/html/classes/XML.html +0 -202
  75. data/html/classes/XML/Document.html +0 -125
  76. data/html/classes/XML/Node.html +0 -124
  77. data/html/created.rid +0 -1
  78. data/html/files/CHANGELOG.html +0 -107
  79. data/html/files/MIT-LICENSE.html +0 -129
  80. data/html/files/README_txt.html +0 -259
  81. data/html/files/lib/s33r/bucket_listing_rb.html +0 -101
  82. data/html/files/lib/s33r/builder_rb.html +0 -108
  83. data/html/files/lib/s33r/client_rb.html +0 -111
  84. data/html/files/lib/s33r/core_rb.html +0 -113
  85. data/html/files/lib/s33r/libxml_extensions_rb.html +0 -101
  86. data/html/files/lib/s33r/libxml_loader_rb.html +0 -109
  87. data/html/files/lib/s33r/logging_rb.html +0 -108
  88. data/html/files/lib/s33r/mimetypes_rb.html +0 -120
  89. data/html/files/lib/s33r/named_bucket_rb.html +0 -101
  90. data/html/files/lib/s33r/s33r_exception_rb.html +0 -101
  91. data/html/files/lib/s33r/s33r_http_rb.html +0 -108
  92. data/html/files/lib/s33r/s3_acl_rb.html +0 -108
  93. data/html/files/lib/s33r/s3_obj_rb.html +0 -108
  94. data/html/files/lib/s33r/sync_rb.html +0 -101
  95. data/html/files/lib/s33r_rb.html +0 -101
  96. data/html/fr_class_index.html +0 -66
  97. data/html/fr_file_index.html +0 -44
  98. data/html/fr_method_index.html +0 -183
  99. data/html/index.html +0 -24
  100. data/html/rdoc-style.css +0 -208
  101. data/lib/s33r/core.rb +0 -296
  102. data/lib/s33r/logging.rb +0 -43
  103. data/lib/s33r/named_bucket.rb +0 -148
  104. data/lib/s33r/sync.rb +0 -13
  105. data/test/cases/spec_all_buckets.rb +0 -28
  106. data/test/cases/spec_client.rb +0 -101
  107. data/test/cases/spec_core.rb +0 -128
  108. data/test/cases/spec_namedbucket.rb +0 -46
  109. data/test/cases/spec_sync.rb +0 -34
  110. data/test/files/all_buckets.xml +0 -21
  111. data/test/files/client_config.yml +0 -5
  112. data/test/files/namedbucket_config.yml +0 -8
  113. data/test/files/namedbucket_config2.yml +0 -8
  114. data/test/test_bucket_setup.rb +0 -41
@@ -0,0 +1,197 @@
1
+ require 'uri'
2
+ require 'openssl'
3
+ require 'net/http'
4
+ require 'net/https'
5
+ require 'stringio'
6
+
7
+ base = File.dirname(__FILE__)
8
+ require File.join(base, 's33r_exception')
9
+ require File.join(base, 's33r_http')
10
+ require File.join(base, 'utility')
11
+
12
+ # Core functionality for managing HTTP requests to S3.
13
+ module S33r
14
+ module Networking
15
+ include S3Exception
16
+ include Net
17
+
18
+ #-- These are specific to the mechanics of the HTTP request.
19
+
20
+ # Net::HTTP instance.
21
+ attr_accessor :client
22
+
23
+ # Are HTTP connections persistent?
24
+ attr_accessor :persistent
25
+
26
+ # Default chunk size for connections.
27
+ attr_accessor :chunk_size
28
+
29
+ # Should requests be dumped?
30
+ attr_accessor :dump_requests
31
+
32
+ # The last response received by the client.
33
+ attr_reader :last_response
34
+
35
+ # Get default options to use on every response.
36
+ def request_defaults
37
+ @request_defaults || {}
38
+ end
39
+
40
+ # Set the defaults.
41
+ def request_defaults=(options={})
42
+ @request_defaults = options
43
+ end
44
+
45
+ # Send a request over the wire.
46
+ #
47
+ # This method streams +data+ if it responds to the +stat+ method
48
+ # (as file handles do).
49
+ #
50
+ # Keys for +headers+ should be strings (e.g. 'Content-Type').
51
+ #
52
+ # +url_options+ is a standard set of options acceptable to s3_url;
53
+ # if any options aren't set, they are set using the +request_defaults+ method
54
+ # (options passed in here override defaults).
55
+ #
56
+ # You can also pass <tt>:authenticated => false</tt> if you want to visit a
57
+ # public URL here; otherwise, an Authorization header is generated and sent. If the client
58
+ # doesn't have an access key or secret access key specified, however, trying to create
59
+ # an access key will result in an error being raised.
60
+ #
61
+ # Returns a Net::HTTPResponse instance.
62
+ def do_request(method, url_options={}, data=nil, headers={})
63
+ # Use the default settings only if not specified in url_options.
64
+ url_options = request_defaults.merge(url_options)
65
+
66
+ # Get the URL.
67
+ url = s3_url(url_options)
68
+ uri = URI(url)
69
+
70
+ # Bar any except the allowed methods.
71
+ raise MethodNotAllowed, "The #{method} HTTP method is not supported" unless METHOD_VERBS.include?(method)
72
+
73
+ # Get a requester.
74
+ path = uri.path
75
+ path += "?" + uri.query if uri.query
76
+ req = eval("HTTP::" + method[0,1].upcase + method[1..-1].downcase + ".new('#{path}')")
77
+
78
+ req.chunk_size = chunk_size || DEFAULT_CHUNK_SIZE
79
+
80
+ # Add the S3 headers which are always required.
81
+ headers.merge!(default_headers(headers))
82
+
83
+ # Headers for canned ACL
84
+ headers.merge! canned_acl_header(url_options[:canned_acl]) if 'PUT' == method
85
+
86
+ # Generate the S3 authorization header if the client has
87
+ # the appropriate instance variable getters.
88
+ unless (false == url_options[:authenticated])
89
+ headers['Authorization'] = generate_auth_header_value(method, path, headers,
90
+ url_options[:access], url_options[:secret])
91
+ end
92
+
93
+ # Insert the headers into the request object.
94
+ headers.each do |key, value|
95
+ req[key] = value
96
+ end
97
+
98
+ # Add data to the request as a stream.
99
+ if req.request_body_permitted?
100
+ # For streaming files; NB Content-Length will be set by Net::HTTP
101
+ # for character-based data: this section of is only used
102
+ # when reading directly from a file.
103
+ if data.respond_to?(:stat)
104
+ length = data.stat.size
105
+ # Strings can be streamed too.
106
+ elsif data.is_a?(String)
107
+ length = data.length
108
+ data = StringIO.new(data)
109
+ else
110
+ length = 0
111
+ end
112
+
113
+ # Data can be streamed if it responds to the read method.
114
+ if data.respond_to?(:read)
115
+ req.body_stream = data
116
+ req['Content-Length'] = length.to_s
117
+ data = nil
118
+ end
119
+ else
120
+ data = nil
121
+ end
122
+
123
+ puts req.to_s if @dump_requests
124
+
125
+ # Set up the request.
126
+ ### <snip> start shameless stealing from Marcel Molina
127
+ request_runner = Proc.new do
128
+ response = @client.request(req, data)
129
+
130
+ # Add some nice messages to the response (like the S3 error
131
+ # message if it occurred).
132
+ response.conveniencify(method)
133
+ @last_response = response
134
+
135
+ return response
136
+ end
137
+
138
+ # Get a client instance.
139
+ init_client(uri)
140
+
141
+ # Run the request.
142
+ if persistent
143
+ @client.start unless @client.started?
144
+ response = request_runner.call
145
+ else
146
+ response = @client.start(&request_runner)
147
+ end
148
+
149
+ response
150
+ ### </snip> end shameless stealing from Marcel Molina
151
+ end
152
+
153
+ # Setup an HTTP client instance.
154
+ #
155
+ # Note that when you send your first request, the client is set
156
+ # up using whichever parameters for host and port you passed the first
157
+ # time. If you change the host or port, the client will be regenerated.
158
+ #
159
+ # +url+ is a URI instance generated from a full URL, including a host
160
+ # name and scheme.
161
+ def init_client(url)
162
+ host = url.host || HOST
163
+ port = url.port
164
+ if @client.nil? or @client.port != port or @client.address != host
165
+ @client = HTTP.new(host, port)
166
+ @client.use_ssl = false
167
+
168
+ # Check whether client needs to use SSL.
169
+ if port == PORT
170
+ # turn off SSL certificate verification
171
+ @client.verify_mode = OpenSSL::SSL::VERIFY_NONE
172
+ @client.use_ssl = true
173
+ end
174
+ end
175
+ end
176
+
177
+ # Perform a get request.
178
+ def do_get(options={}, headers={})
179
+ do_request('GET', options, nil, headers)
180
+ end
181
+
182
+ # Perform a put request.
183
+ def do_put(data, options={}, headers={})
184
+ do_request('PUT', options, data, headers)
185
+ end
186
+
187
+ # Perform a delete request.
188
+ def do_delete(options={}, headers={})
189
+ do_request('DELETE', options, headers)
190
+ end
191
+
192
+ # Perform a head request.
193
+ def do_head(options={}, headers={})
194
+ do_request('HEAD', options, headers)
195
+ end
196
+ end
197
+ end
@@ -1,25 +1,38 @@
1
1
  module S33r
2
- module S33rException
3
-
4
- class MethodNotAvailable < Exception
2
+ module S3Exception
3
+
4
+ # For errors originated by S3
5
+ class S3OriginatedException < Exception
6
+ attr_reader :s3_code, :s3_message
7
+
8
+ def initialize(code=nil, message=nil)
9
+ @s3_code = code
10
+ @s3_message = message
11
+ end
12
+
13
+ def to_s
14
+ "S3 returned an error\nError code: #{@s3_code}; error message: #{@s3_message}"
15
+ end
16
+ end
17
+
18
+ def self.error(s3_code, s3_message=nil)
19
+ S3OriginatedException.new(s3_code, s3_message)
5
20
  end
6
21
 
22
+ # Errors originated by S33r.
23
+ class MethodNotAllowed < Exception
24
+ end
25
+
7
26
  class MissingRequiredHeaders < Exception
8
27
  end
9
28
 
10
29
  class UnsupportedCannedACL < Exception
11
30
  end
12
31
 
13
- class UnsupportedHTTPMethod < Exception
14
- end
15
-
16
32
  class MalformedBucketName < Exception
17
33
  end
18
34
 
19
- class MissingBucketName < Exception
20
- end
21
-
22
- class MissingResource < Exception
35
+ class InvalidBucket < Exception
23
36
  end
24
37
 
25
38
  class BucketListingMaxKeysError < Exception
@@ -28,23 +41,21 @@ module S33r
28
41
  class InvalidBucketListing < Exception
29
42
  end
30
43
 
31
- class S3FallenOver < Exception
44
+ class InvalidGroupType < Exception
32
45
  end
33
46
 
34
- class InvalidS3GroupType < Exception
47
+ class InvalidPermission < Exception
35
48
  end
36
49
 
37
- class InvalidPermission < Exception
50
+ # Raised if an attempt is made to generate an authenticated URL
51
+ # with one of the required AWS keys missing.
52
+ class KeysIncomplete < Exception
38
53
  end
39
54
 
40
55
  # Raised if a bucket cannot be used as a log target
41
- # (i.e. if LogDelivery permissions not set - see S33r::S3ACL::ACLDoc.add_log_target_grants)
56
+ # (i.e. if LogDelivery permissions not set - see S33r::S3ACL::Policy.add_log_target_grants)
42
57
  class BucketNotLogTargetable < Exception
43
58
  end
44
-
45
- # Raised if you try to do a put with empty body
46
- class TryingToPutEmptyResource < Exception
47
- end
48
59
 
49
60
  end
50
61
  end
@@ -1,31 +1,48 @@
1
1
  require 'net/http'
2
- require File.join(File.dirname(__FILE__), 's33r_exception')
2
+ base = File.dirname(__FILE__)
3
+ require File.join(base, 's33r_exception')
4
+ require File.join(base, 'utility')
5
+ require File.join(base, 'libxml_loader')
6
+ require File.join(base, 'libxml_extensions')
3
7
 
4
- # Addtions to the Net::HTTP classes
5
- #
6
- # Adds some convenience functions for checking response status.
8
+ # Additions to the Net::HTTP classes
9
+
10
+ # Adds some convenience functions for checking response status, parsing
11
+ # error messages from
7
12
  class Net::HTTPResponse
8
- attr_accessor :success
9
13
  attr_writer :body
10
14
 
11
- def check_s3_availability
12
- if (500..503).include? code.to_i
13
- raise S33r::S33rException::S3FallenOver, "S3 has fallen over! (got a #{code} code in response)"
15
+ # An exception wrapped around the response from S3
16
+ attr_reader :s3_error
17
+
18
+ # Wrap responses properly so the S3 error codes don't have to be read.
19
+ def conveniencify(method)
20
+ if '200' == code
21
+ @ok = true
22
+ @s3_message = 'Request successful'
23
+ # 204 returned for successful bucket or item deletes
24
+ elsif '204' == code and 'DELETE' == method
25
+ @s3_message = 'Successfully deleted'
26
+ @ok = true
27
+ else
28
+ @ok = false
29
+ if body.nil?
30
+ @s3_error = S33r::S3Exception.error(code)
31
+ else
32
+ # Get the S3 error message and code
33
+ error_response = XML.get_xml_doc(body)
34
+ s3_code = error_response.xget('//Code')
35
+ s3_message = error_response.xget('//Message')
36
+
37
+ @s3_error = S33r::S3Exception.error(s3_code, s3_message)
38
+ end
14
39
  end
15
40
  end
16
41
 
17
42
  def ok?
18
- success
19
- end
20
-
21
- def success
22
- code == '200'
23
- end
24
-
25
- def not_found
26
- code = '404'
43
+ @ok || false
27
44
  end
28
-
45
+
29
46
  def to_s
30
47
  body
31
48
  end
@@ -52,6 +69,7 @@ class Net::HTTPGenericRequest
52
69
  self.each_capitalized do |key, value|
53
70
  str += "#{key}: #{value}\n"
54
71
  end
72
+ str += "*******\n" + body + "\n" unless body.nil?
55
73
  str += "*******\n\n"
56
74
  str
57
75
  end
data/lib/s33r/s3_acl.rb CHANGED
@@ -15,9 +15,9 @@ module S33r
15
15
  #
16
16
  # Represents both retrieved ACL XML or can be built up
17
17
  # using objects and converted to XML.
18
- # NB the ACLDoc is oblivious to the resource it is going
18
+ # NB the Policy is oblivious to the resource it is going
19
19
  # to be applied to.
20
- class ACLDoc
20
+ class Policy
21
21
  # List of grants to be applied.
22
22
  attr_accessor :grants, :owner
23
23
 
@@ -27,7 +27,7 @@ module S33r
27
27
  @owner = owner
28
28
  end
29
29
 
30
- # Create an ACLDoc instance from a raw Access Control Policy XML document.
30
+ # Create an Policy instance from a raw Access Control Policy XML document.
31
31
  #
32
32
  # +acl_xml+ is a raw Access Control Policy XML string (NOT libxml Document or Node).
33
33
  #
@@ -50,7 +50,7 @@ module S33r
50
50
  grants << Grant.new(grantee, permission)
51
51
  end
52
52
 
53
- ACLDoc.new(owner, grants)
53
+ Policy.new(owner, grants)
54
54
  end
55
55
 
56
56
  # Generate AccessControlPolicy XML document.
@@ -81,12 +81,10 @@ module S33r
81
81
  # Returns true if grant was added;
82
82
  # false otherwise (grant already exists).
83
83
  def add_grant(grant)
84
- if @grants.include?(grant)
85
- return false
86
- else
84
+ unless @grants.include?(grant)
87
85
  @grants << grant
88
- return true
89
86
  end
87
+ self
90
88
  end
91
89
 
92
90
  # Remove a grant from the ACL document. Note that if you
@@ -100,6 +98,7 @@ module S33r
100
98
  # false if it wasn't in the document.
101
99
  def remove_grant(grant)
102
100
  @grants.delete_if { |g| grant == g }
101
+ self
103
102
  end
104
103
 
105
104
  # Does the ACL contain a grant for public reads?
@@ -117,42 +116,15 @@ module S33r
117
116
  add_grant(Grant.public_read_grant)
118
117
  end
119
118
 
120
- # Does the ACL make the associated resource available as a log target?
121
- def log_targetable?
122
- log_target_grants = Grant.log_target_grants
123
- log_target_grants.each { |g| return false if !grants.include?(g) }
124
- return true
125
- end
126
-
127
- # Add permissions to an instances which give READ_ACL
128
- # and WRITE permissions to the LogDelivery group. Used
129
- # to enable a bucket as a logging destination.
130
- #
131
- # Returns true if grants added, false otherwise
132
- # (if already a log target).
133
- def add_log_target_grants
134
- if log_targetable?
135
- return false
136
- else
137
- Grant.log_target_grants.each { |g| add_grant(g) }
138
- return true
139
- end
119
+ # Remove the public READ permission from this instance.
120
+ def remove_public_read_grant
121
+ remove_grant(Grant.public_read_grant)
140
122
  end
141
123
 
142
- # Remove log target ACLs from the document.
143
- #
144
- # Returns true if all log target grants were removed;
145
- # false otherwise.
146
- #
147
- # NB even if this method returns false, that doesn't mean
148
- # the bucket is still a log target. Use log_targetable? to check
149
- # whether a bucket can be used as a log target.
150
- def remove_log_target_grants
151
- ok = true
152
- Grant.log_target_grants.each { |g| ok = ok and remove_grant(g) }
153
- ok
124
+ # String representation of the policy.
125
+ def to_s
126
+ @grants.inject('') {|acc, grant| acc += "* " + grant.to_s + "\n"}
154
127
  end
155
-
156
128
  end
157
129
 
158
130
  # Representation of an S3 Grant
@@ -171,7 +143,8 @@ module S33r
171
143
  else
172
144
  @permission = PERMISSIONS[permission]
173
145
  end
174
- raise InvalidPermission, "Permission #{permission.to_s} is not a valid permission specifier" if @permission.nil?
146
+ raise S3Exception::InvalidPermission, \
147
+ "Permission #{permission.to_s} is not a valid permission specifier" if @permission.nil?
175
148
  end
176
149
 
177
150
  # Note that setting a grant for an Amazon customer is the
@@ -196,15 +169,6 @@ module S33r
196
169
  Grant.new(Group.new(:all_users), :read)
197
170
  end
198
171
 
199
- # Generator for a grant which gives the LogDelivery group
200
- # write and read_acl permissions on a bucket.
201
- #
202
- # Returns an array with the two required Grant instances.
203
- def Grant.log_target_grants
204
- log_delivery_group = Group.new(:log_delivery)
205
- [Grant.new(log_delivery_group, :read_acl), Grant.new(log_delivery_group, :write)]
206
- end
207
-
208
172
  # Convert a Grant object into an XML fragment.
209
173
  def to_xml
210
174
  xml_str = ""
@@ -239,6 +203,10 @@ module S33r
239
203
  return true
240
204
  end
241
205
 
206
+ def to_s
207
+ "#{@grantee.to_s} has permission #{@permission}"
208
+ end
209
+
242
210
  end
243
211
 
244
212
  # Abstract representation of an S3 Grantee.
@@ -291,6 +259,10 @@ module S33r
291
259
  @grantee_type = GRANTEE_TYPES[:amazon_customer]
292
260
  @email_address = email_address
293
261
  end
262
+
263
+ def to_s
264
+ "Amazon customer with address '#{@email_address}"
265
+ end
294
266
  end
295
267
 
296
268
  # An S3 user.
@@ -315,6 +287,10 @@ module S33r
315
287
  display_name = user_xml_doc.xget('DisplayName')
316
288
  new(user_id, display_name)
317
289
  end
290
+
291
+ def to_s
292
+ "Canonical user '#{@display_name}' (with user ID '#{@user_id}')"
293
+ end
318
294
  end
319
295
 
320
296
  # One of the predefined S3 groups.
@@ -327,11 +303,15 @@ module S33r
327
303
  # one of the pre-defined Amazon group types.
328
304
  def initialize(group_type)
329
305
  unless S3_GROUP_TYPES.has_key?(group_type)
330
- raise InvalidS3GroupType, 'No such group type #{group_type}'
306
+ raise S3Exception::InvalidGroupType, 'No such group type #{group_type}'
331
307
  end
332
308
  @group_type = S3_GROUP_TYPES[group_type]
333
309
  @grantee_type = GRANTEE_TYPES[:group]
334
310
  end
311
+
312
+ def to_s
313
+ "Group '#{@group_type}'"
314
+ end
335
315
  end
336
316
 
337
317
  end