aws-sdk 1.0.0

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 (205) hide show
  1. data/.yardopts +6 -0
  2. data/LICENSE.txt +171 -0
  3. data/NOTICE.txt +2 -0
  4. data/README.rdoc +189 -0
  5. data/lib/aws-sdk.rb +14 -0
  6. data/lib/aws.rb +63 -0
  7. data/lib/aws/api_config.rb +45 -0
  8. data/lib/aws/api_config/.document +0 -0
  9. data/lib/aws/api_config/EC2-2011-02-28.yml +2314 -0
  10. data/lib/aws/api_config/SNS-2010-03-31.yml +171 -0
  11. data/lib/aws/api_config/SQS-2009-02-01.yml +161 -0
  12. data/lib/aws/api_config/SimpleDB-2009-04-15.yml +278 -0
  13. data/lib/aws/api_config/SimpleEmailService-2010-12-01.yml +147 -0
  14. data/lib/aws/api_config_transform.rb +32 -0
  15. data/lib/aws/async_handle.rb +90 -0
  16. data/lib/aws/authorize_v2.rb +37 -0
  17. data/lib/aws/authorize_v3.rb +37 -0
  18. data/lib/aws/base_client.rb +524 -0
  19. data/lib/aws/cacheable.rb +92 -0
  20. data/lib/aws/common.rb +228 -0
  21. data/lib/aws/configurable.rb +36 -0
  22. data/lib/aws/configuration.rb +272 -0
  23. data/lib/aws/configured_client_methods.rb +81 -0
  24. data/lib/aws/configured_grammars.rb +65 -0
  25. data/lib/aws/configured_option_grammars.rb +46 -0
  26. data/lib/aws/configured_xml_grammars.rb +47 -0
  27. data/lib/aws/default_signer.rb +38 -0
  28. data/lib/aws/ec2.rb +321 -0
  29. data/lib/aws/ec2/attachment.rb +149 -0
  30. data/lib/aws/ec2/attachment_collection.rb +57 -0
  31. data/lib/aws/ec2/availability_zone.rb +80 -0
  32. data/lib/aws/ec2/availability_zone_collection.rb +47 -0
  33. data/lib/aws/ec2/block_device_mappings.rb +53 -0
  34. data/lib/aws/ec2/client.rb +54 -0
  35. data/lib/aws/ec2/client/xml.rb +127 -0
  36. data/lib/aws/ec2/collection.rb +39 -0
  37. data/lib/aws/ec2/config_transform.rb +63 -0
  38. data/lib/aws/ec2/elastic_ip.rb +107 -0
  39. data/lib/aws/ec2/elastic_ip_collection.rb +85 -0
  40. data/lib/aws/ec2/errors.rb +29 -0
  41. data/lib/aws/ec2/filtered_collection.rb +65 -0
  42. data/lib/aws/ec2/has_permissions.rb +46 -0
  43. data/lib/aws/ec2/image.rb +245 -0
  44. data/lib/aws/ec2/image_collection.rb +235 -0
  45. data/lib/aws/ec2/instance.rb +515 -0
  46. data/lib/aws/ec2/instance_collection.rb +276 -0
  47. data/lib/aws/ec2/key_pair.rb +86 -0
  48. data/lib/aws/ec2/key_pair_collection.rb +102 -0
  49. data/lib/aws/ec2/permission_collection.rb +177 -0
  50. data/lib/aws/ec2/region.rb +81 -0
  51. data/lib/aws/ec2/region_collection.rb +55 -0
  52. data/lib/aws/ec2/request.rb +27 -0
  53. data/lib/aws/ec2/reserved_instances.rb +50 -0
  54. data/lib/aws/ec2/reserved_instances_collection.rb +44 -0
  55. data/lib/aws/ec2/reserved_instances_offering.rb +55 -0
  56. data/lib/aws/ec2/reserved_instances_offering_collection.rb +43 -0
  57. data/lib/aws/ec2/resource.rb +340 -0
  58. data/lib/aws/ec2/resource_tag_collection.rb +218 -0
  59. data/lib/aws/ec2/security_group.rb +246 -0
  60. data/lib/aws/ec2/security_group/ip_permission.rb +70 -0
  61. data/lib/aws/ec2/security_group/ip_permission_collection.rb +59 -0
  62. data/lib/aws/ec2/security_group_collection.rb +132 -0
  63. data/lib/aws/ec2/snapshot.rb +138 -0
  64. data/lib/aws/ec2/snapshot_collection.rb +90 -0
  65. data/lib/aws/ec2/tag.rb +88 -0
  66. data/lib/aws/ec2/tag_collection.rb +114 -0
  67. data/lib/aws/ec2/tagged_collection.rb +48 -0
  68. data/lib/aws/ec2/tagged_item.rb +87 -0
  69. data/lib/aws/ec2/volume.rb +190 -0
  70. data/lib/aws/ec2/volume_collection.rb +95 -0
  71. data/lib/aws/errors.rb +129 -0
  72. data/lib/aws/http/builtin_handler.rb +69 -0
  73. data/lib/aws/http/curb_handler.rb +123 -0
  74. data/lib/aws/http/handler.rb +77 -0
  75. data/lib/aws/http/httparty_handler.rb +61 -0
  76. data/lib/aws/http/request.rb +136 -0
  77. data/lib/aws/http/request_param.rb +63 -0
  78. data/lib/aws/http/response.rb +75 -0
  79. data/lib/aws/ignore_result_element.rb +38 -0
  80. data/lib/aws/indifferent_hash.rb +86 -0
  81. data/lib/aws/inflection.rb +46 -0
  82. data/lib/aws/lazy_error_classes.rb +64 -0
  83. data/lib/aws/meta_utils.rb +43 -0
  84. data/lib/aws/model.rb +57 -0
  85. data/lib/aws/naming.rb +32 -0
  86. data/lib/aws/option_grammar.rb +544 -0
  87. data/lib/aws/policy.rb +912 -0
  88. data/lib/aws/rails.rb +209 -0
  89. data/lib/aws/record.rb +79 -0
  90. data/lib/aws/record/attribute.rb +94 -0
  91. data/lib/aws/record/attribute_macros.rb +288 -0
  92. data/lib/aws/record/attributes/boolean.rb +49 -0
  93. data/lib/aws/record/attributes/datetime.rb +86 -0
  94. data/lib/aws/record/attributes/float.rb +48 -0
  95. data/lib/aws/record/attributes/integer.rb +68 -0
  96. data/lib/aws/record/attributes/sortable_float.rb +60 -0
  97. data/lib/aws/record/attributes/sortable_integer.rb +95 -0
  98. data/lib/aws/record/attributes/string.rb +69 -0
  99. data/lib/aws/record/base.rb +728 -0
  100. data/lib/aws/record/conversion.rb +38 -0
  101. data/lib/aws/record/dirty_tracking.rb +286 -0
  102. data/lib/aws/record/errors.rb +153 -0
  103. data/lib/aws/record/exceptions.rb +48 -0
  104. data/lib/aws/record/finder_methods.rb +262 -0
  105. data/lib/aws/record/naming.rb +31 -0
  106. data/lib/aws/record/scope.rb +157 -0
  107. data/lib/aws/record/validations.rb +653 -0
  108. data/lib/aws/record/validator.rb +237 -0
  109. data/lib/aws/record/validators/acceptance.rb +51 -0
  110. data/lib/aws/record/validators/block.rb +38 -0
  111. data/lib/aws/record/validators/confirmation.rb +43 -0
  112. data/lib/aws/record/validators/count.rb +108 -0
  113. data/lib/aws/record/validators/exclusion.rb +43 -0
  114. data/lib/aws/record/validators/format.rb +57 -0
  115. data/lib/aws/record/validators/inclusion.rb +56 -0
  116. data/lib/aws/record/validators/length.rb +107 -0
  117. data/lib/aws/record/validators/numericality.rb +138 -0
  118. data/lib/aws/record/validators/presence.rb +45 -0
  119. data/lib/aws/resource_cache.rb +39 -0
  120. data/lib/aws/response.rb +113 -0
  121. data/lib/aws/response_cache.rb +50 -0
  122. data/lib/aws/s3.rb +109 -0
  123. data/lib/aws/s3/access_control_list.rb +252 -0
  124. data/lib/aws/s3/acl_object.rb +266 -0
  125. data/lib/aws/s3/bucket.rb +320 -0
  126. data/lib/aws/s3/bucket_collection.rb +122 -0
  127. data/lib/aws/s3/bucket_version_collection.rb +85 -0
  128. data/lib/aws/s3/client.rb +999 -0
  129. data/lib/aws/s3/client/xml.rb +190 -0
  130. data/lib/aws/s3/data_options.rb +99 -0
  131. data/lib/aws/s3/errors.rb +43 -0
  132. data/lib/aws/s3/multipart_upload.rb +318 -0
  133. data/lib/aws/s3/multipart_upload_collection.rb +78 -0
  134. data/lib/aws/s3/object_collection.rb +159 -0
  135. data/lib/aws/s3/object_metadata.rb +67 -0
  136. data/lib/aws/s3/object_upload_collection.rb +83 -0
  137. data/lib/aws/s3/object_version.rb +141 -0
  138. data/lib/aws/s3/object_version_collection.rb +78 -0
  139. data/lib/aws/s3/paginated_collection.rb +94 -0
  140. data/lib/aws/s3/policy.rb +76 -0
  141. data/lib/aws/s3/prefix_and_delimiter_collection.rb +56 -0
  142. data/lib/aws/s3/prefixed_collection.rb +84 -0
  143. data/lib/aws/s3/presigned_post.rb +504 -0
  144. data/lib/aws/s3/request.rb +198 -0
  145. data/lib/aws/s3/s3_object.rb +794 -0
  146. data/lib/aws/s3/tree.rb +116 -0
  147. data/lib/aws/s3/tree/branch_node.rb +71 -0
  148. data/lib/aws/s3/tree/child_collection.rb +108 -0
  149. data/lib/aws/s3/tree/leaf_node.rb +99 -0
  150. data/lib/aws/s3/tree/node.rb +22 -0
  151. data/lib/aws/s3/tree/parent.rb +90 -0
  152. data/lib/aws/s3/uploaded_part.rb +82 -0
  153. data/lib/aws/s3/uploaded_part_collection.rb +86 -0
  154. data/lib/aws/service_interface.rb +60 -0
  155. data/lib/aws/simple_db.rb +202 -0
  156. data/lib/aws/simple_db/attribute.rb +159 -0
  157. data/lib/aws/simple_db/attribute_collection.rb +227 -0
  158. data/lib/aws/simple_db/client.rb +52 -0
  159. data/lib/aws/simple_db/client/options.rb +34 -0
  160. data/lib/aws/simple_db/client/xml.rb +68 -0
  161. data/lib/aws/simple_db/consistent_read_option.rb +42 -0
  162. data/lib/aws/simple_db/delete_attributes.rb +64 -0
  163. data/lib/aws/simple_db/domain.rb +118 -0
  164. data/lib/aws/simple_db/domain_collection.rb +116 -0
  165. data/lib/aws/simple_db/domain_metadata.rb +112 -0
  166. data/lib/aws/simple_db/errors.rb +46 -0
  167. data/lib/aws/simple_db/expect_condition_option.rb +45 -0
  168. data/lib/aws/simple_db/item.rb +84 -0
  169. data/lib/aws/simple_db/item_collection.rb +594 -0
  170. data/lib/aws/simple_db/item_data.rb +70 -0
  171. data/lib/aws/simple_db/put_attributes.rb +62 -0
  172. data/lib/aws/simple_db/request.rb +27 -0
  173. data/lib/aws/simple_email_service.rb +373 -0
  174. data/lib/aws/simple_email_service/client.rb +39 -0
  175. data/lib/aws/simple_email_service/client/options.rb +24 -0
  176. data/lib/aws/simple_email_service/client/xml.rb +38 -0
  177. data/lib/aws/simple_email_service/email_address_collection.rb +66 -0
  178. data/lib/aws/simple_email_service/errors.rb +29 -0
  179. data/lib/aws/simple_email_service/quotas.rb +64 -0
  180. data/lib/aws/simple_email_service/request.rb +27 -0
  181. data/lib/aws/sns.rb +69 -0
  182. data/lib/aws/sns/client.rb +37 -0
  183. data/lib/aws/sns/client/options.rb +24 -0
  184. data/lib/aws/sns/client/xml.rb +38 -0
  185. data/lib/aws/sns/errors.rb +29 -0
  186. data/lib/aws/sns/policy.rb +49 -0
  187. data/lib/aws/sns/request.rb +27 -0
  188. data/lib/aws/sns/subscription.rb +100 -0
  189. data/lib/aws/sns/subscription_collection.rb +84 -0
  190. data/lib/aws/sns/topic.rb +384 -0
  191. data/lib/aws/sns/topic_collection.rb +70 -0
  192. data/lib/aws/sns/topic_subscription_collection.rb +58 -0
  193. data/lib/aws/sqs.rb +70 -0
  194. data/lib/aws/sqs/client.rb +38 -0
  195. data/lib/aws/sqs/client/xml.rb +36 -0
  196. data/lib/aws/sqs/errors.rb +33 -0
  197. data/lib/aws/sqs/policy.rb +50 -0
  198. data/lib/aws/sqs/queue.rb +507 -0
  199. data/lib/aws/sqs/queue_collection.rb +105 -0
  200. data/lib/aws/sqs/received_message.rb +184 -0
  201. data/lib/aws/sqs/received_sns_message.rb +112 -0
  202. data/lib/aws/sqs/request.rb +44 -0
  203. data/lib/aws/xml_grammar.rb +923 -0
  204. data/rails/init.rb +15 -0
  205. metadata +298 -0
@@ -0,0 +1,122 @@
1
+ # Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ require 'aws/model'
15
+ require 'aws/s3/bucket'
16
+
17
+ module AWS
18
+ class S3
19
+
20
+ # Represents a collection of buckets.
21
+ #
22
+ # You can use this to create a bucket:
23
+ #
24
+ # s3.buckets.create(:name => "mybucket")
25
+ #
26
+ # You can get a handle for a specific bucket with indifferent
27
+ # access:
28
+ #
29
+ # bucket = s3.buckets[:mybucket]
30
+ # bucket = s3.buckets['mybucket']
31
+ #
32
+ # You can also use it to find out which buckets are in your account:
33
+ #
34
+ # s3.buckets.collect(&:name)
35
+ # #=> ['bucket1', 'bucket2', ...]
36
+ #
37
+ class BucketCollection
38
+
39
+ include Model
40
+ include Enumerable
41
+
42
+ # Creates and returns a new Bucket. For example:
43
+ #
44
+ # @example
45
+ #
46
+ # bucket = s3.buckets.create('my-bucket')
47
+ # bucket.name #=> "my-bucket"
48
+ # bucket.exists? #=> true
49
+ #
50
+ # @param [String] bucket_name
51
+ # @param [Hash] options
52
+ # @option options [String] :location_constraint (nil) The location
53
+ # where the bucket should be created. Defaults to the classic
54
+ # US region.
55
+ # @option options [String] :acl (:private) Sets the ACL of the bucket
56
+ # you are creating. Valid Values include :private, :public_read,
57
+ # :public_read_write, :authenticated_read, :bucket_owner_read and
58
+ # :bucket_owner_full_control
59
+ # @return [Bucket]
60
+ def create(bucket_name, options = {})
61
+
62
+ # auto set the location constraint for the user if it is not
63
+ # passed in and the endpoint is not the us-standard region. don't
64
+ # override the location constraint though, even it is wrong,
65
+ unless
66
+ config.s3_endpoint == 's3.amazonaws.com' or
67
+ options[:location_constraint]
68
+ then
69
+ options[:location_constraint] = case config.s3_endpoint
70
+ when 's3-eu-west-1.amazonaws.com' then 'EU'
71
+ else config.s3_endpoint.match(/^s3-(.*)\.amazonaws\.com$/)[1]
72
+ end
73
+ end
74
+
75
+ client.create_bucket(options.merge(:bucket_name => bucket_name))
76
+ bucket_named(bucket_name)
77
+
78
+ end
79
+
80
+ # Returns the Bucket with the given name.
81
+ #
82
+ # Makes no requests. The returned bucket object can
83
+ # be used to make requets for the bucket and its objects.
84
+ #
85
+ # @example
86
+ #
87
+ # bucket = s3.buckets[:mybucket],
88
+ # bucket = s3.buckets['mybucket'],
89
+ #
90
+ # @param [String] bucket_name
91
+ # @return [Bucket]
92
+ def [] bucket_name
93
+ bucket_named(bucket_name)
94
+ end
95
+
96
+ # Iterates the buckets in this collection.
97
+ #
98
+ # @example
99
+ #
100
+ # s3.buckets.each do |bucket|
101
+ # puts bucket.name
102
+ # end
103
+ #
104
+ # @return [nil]
105
+ def each &block
106
+ response = client.list_buckets
107
+ response.buckets.each do |b|
108
+ yield(bucket_named(b.name, response.owner))
109
+ end
110
+ nil
111
+ end
112
+
113
+ # @private
114
+ private
115
+ def bucket_named name, owner = nil
116
+ S3::Bucket.new(name.to_s, :owner => owner, :config => config)
117
+ end
118
+
119
+ end
120
+
121
+ end
122
+ end
@@ -0,0 +1,85 @@
1
+ # Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ require 'aws/model'
15
+ require 'aws/s3/object_version'
16
+ require 'aws/s3/prefix_and_delimiter_collection'
17
+
18
+ module AWS
19
+ class S3
20
+
21
+ # A collection of versioned objects for the entire bucket.
22
+ #
23
+ # @see PrefixedCollection
24
+ class BucketVersionCollection
25
+
26
+ include Model
27
+ include Enumerable
28
+ include PrefixAndDelimiterCollection
29
+
30
+ # @param [Bucket] bucket
31
+ def initialize bucket, options = {}
32
+ @bucket = bucket
33
+ super
34
+ end
35
+
36
+ # @return [Bucket] The bucket this collection belongs to.
37
+ attr_reader :bucket
38
+
39
+ # @return [ObjectVersion] Returns the most recently created object
40
+ # version in the entire bucket.
41
+ def latest
42
+ self.find{|version| true }
43
+ end
44
+
45
+ # Yields once for each version in the bucket.
46
+ #
47
+ # @yield [object_version]
48
+ # @yieldparam [ObjectVersion] object_version
49
+ # @return nil
50
+ def each options = {}, █ super; end
51
+
52
+ # @private
53
+ protected
54
+ def each_member_in_page(page, &block)
55
+ super
56
+ page.versions.each do |version|
57
+ object_version =
58
+ ObjectVersion.new(bucket.objects[version.key],
59
+ version.version_id,
60
+ :delete_marker => version.delete_marker?)
61
+ yield(object_version)
62
+ end
63
+ end
64
+
65
+ # @private
66
+ protected
67
+ def list_request(options)
68
+ client.list_object_versions(options)
69
+ end
70
+
71
+ # @private
72
+ protected
73
+ def limit_param; :max_keys; end
74
+
75
+ # @private
76
+ protected
77
+ def pagination_markers; super + [:version_id_marker]; end
78
+
79
+ # @private
80
+ protected
81
+ def page_size(resp); super + resp.versions.size; end
82
+
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,999 @@
1
+ # Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ require 'aws/s3/request'
15
+ require 'aws/http/response'
16
+ require 'aws/base_client'
17
+ require 'aws/s3/errors'
18
+ require 'aws/s3/data_options'
19
+ require 'aws/s3/access_control_list'
20
+ require 'aws/s3/policy'
21
+ require 'aws/s3/client/xml'
22
+ require 'pathname'
23
+ require 'stringio'
24
+ require 'json'
25
+ require 'rexml/document'
26
+
27
+ module AWS
28
+ class S3
29
+
30
+ ##
31
+ # Provides a low-level client to Amazon S3:
32
+ #
33
+ # * Each method makes exactly one request to S3, and no two
34
+ # methods make the same type of request.
35
+ #
36
+ # * These methods hide the details of how request parameters are
37
+ # sent to S3; for example:
38
+ #
39
+ # client.set_bucket_acl(# controls which host to connect to
40
+ # :bucket_name => "mybucket",
41
+ # # the request payload
42
+ # :acl => [{ :grantee => "..." }])
43
+ #
44
+ # * These methods return subclasses of Response, so that you can
45
+ # always get access to the request that was made and the raw
46
+ # HTTP response. You can also access S3-specific response
47
+ # metadata. For example:
48
+ #
49
+ # response = client.list_buckets
50
+ # response.http_request.http_method # => "GET"
51
+ # response.http_response.body # => "<ListAllMyBucketsResult xmlns..."
52
+ # response.request_id # => "32FE2CEB32F5EE25"
53
+ # # (S3-specific metadata)
54
+ #
55
+ # * This client attempts to raise ArgumentError for any invalid
56
+ # requests it can detect before sending a request to the
57
+ # service. For example:
58
+ #
59
+ # begin
60
+ # client.create_bucket
61
+ # rescue ArgumentError => e
62
+ # puts e # prints "The bucket_name parameter is
63
+ # # required"
64
+ # end
65
+ #
66
+ # * Each method can take an +:async+ to turn it into an
67
+ # asynchronous operation. Instead of blocking on the response
68
+ # to the service call, the method will return a handle on the
69
+ # response. For example:
70
+ #
71
+ # response = client.list_buckets(:async => true)
72
+ # response.on_success { p response.buckets.map(&:name) }
73
+ #
74
+ # @private
75
+ class Client < BaseClient
76
+
77
+ API_VERSION = '2006-03-01'
78
+
79
+ XMLNS = "http://s3.amazonaws.com/doc/#{API_VERSION}/"
80
+
81
+ include DataOptions
82
+
83
+ configure_client
84
+
85
+ protected
86
+ def self.bucket_method(method_name, verb, *args, &block)
87
+ method_options = (args.pop if args.last.kind_of?(Hash)) || {}
88
+ xml_grammar = (args.pop if args.last.respond_to?(:parse))
89
+ verb = verb.to_s.upcase
90
+ subresource = args.first
91
+
92
+ add_client_request_method(method_name, :xml_grammar => xml_grammar) do
93
+
94
+ configure_request do |req, options|
95
+ require_bucket_name!(options[:bucket_name])
96
+ req.http_method = verb
97
+ req.bucket = options[:bucket_name]
98
+ req.add_param(subresource) if subresource
99
+
100
+ if header_options = method_options[:header_options]
101
+ header_options.each do |(option_name, header)|
102
+ req.headers[header] = options[option_name] if
103
+ options[option_name]
104
+ end
105
+ end
106
+
107
+ end
108
+
109
+ instance_eval(&block) if block
110
+ end
111
+ end
112
+
113
+ protected
114
+ def self.object_method(method_name, verb, *args, &block)
115
+ bucket_method(method_name, verb, *args) do
116
+ configure_request do |req, options|
117
+ validate_key!(options[:key])
118
+ super(req, options)
119
+ req.key = options[:key]
120
+ end
121
+
122
+ instance_eval(&block) if block
123
+ end
124
+ end
125
+
126
+ public
127
+
128
+ bucket_method(:create_bucket, :put) do
129
+ configure_request do |req, options|
130
+ validate_bucket_name!(options[:bucket_name])
131
+ req.canned_acl = options[:acl]
132
+ if location = options[:location_constraint]
133
+ xmlns = "http://s3.amazonaws.com/doc/#{API_VERSION}/"
134
+ req.body = <<-XML
135
+ <CreateBucketConfiguration xmlns="#{xmlns}">
136
+ <LocationConstraint>#{location}</LocationConstraint>
137
+ </CreateBucketConfiguration>
138
+ XML
139
+ end
140
+ super(req, options)
141
+ end
142
+ end
143
+
144
+ ##
145
+ # Deletes a bucket.
146
+ #
147
+ # == Required Options
148
+ #
149
+ # * +:bucket_name+ -- The name of the bucket.
150
+ bucket_method(:delete_bucket, :delete)
151
+
152
+ ##
153
+ # Lists the buckets in the account.
154
+ add_client_request_method(:list_buckets) do
155
+ configure_request do |req, options|
156
+ req.http_method = "GET"
157
+ end
158
+
159
+ process_response do |resp|
160
+ XML::ListBuckets.parse(resp.http_response.body, :context => resp)
161
+ end
162
+ end
163
+
164
+ ##
165
+ # Sets the access policy for a bucket.
166
+ #
167
+ # == Required Options
168
+ #
169
+ # * +:bucket_name+ -- The name of the bucket.
170
+ #
171
+ # * +:policy+ -- The new policy. This can be a string (which
172
+ # is assumed to contain a valid policy expressed in JSON), a
173
+ # Policy or any object that responds to +to_json+.
174
+ #
175
+ # == Response
176
+ #
177
+ # The response contains only the standard fields.
178
+ bucket_method(:set_bucket_policy, :put, 'policy') do
179
+
180
+ configure_request do |req, options|
181
+ require_policy!(options[:policy])
182
+ super(req, options)
183
+ policy = options[:policy]
184
+ policy = policy.to_json unless policy.respond_to?(:to_str)
185
+ req.body = policy
186
+ end
187
+
188
+ end
189
+
190
+ ##
191
+ # Gets the access policy for a bucket.
192
+ #
193
+ # == Required Options
194
+ #
195
+ # * +:bucket_name+ -- The name of the bucket.
196
+ #
197
+ # == Response
198
+ #
199
+ # A successful response will have a +policy+ method that
200
+ # returns an instance of Policy.
201
+ #
202
+ bucket_method(:get_bucket_policy, :get, 'policy') do
203
+ process_response do |resp|
204
+ # FIXME: this makes unit testing easier, but is there something
205
+ # we should be doing in case of invalid JSON from the service?
206
+ policy = Policy.from_json(resp.http_response.body) rescue nil
207
+ MetaUtils.extend_method(resp, :policy) { policy }
208
+ end
209
+ end
210
+
211
+ ##
212
+ # Deletes the access policy for a bucket.
213
+ #
214
+ # == Required Options
215
+ #
216
+ # * +:bucket_name+ -- The name of the bucket.
217
+ #
218
+ bucket_method(:delete_bucket_policy, :delete, 'policy')
219
+
220
+ bucket_method(:set_bucket_versioning, :put, 'versioning') do
221
+ configure_request do |req, options|
222
+ state = options[:state].to_s.downcase.capitalize
223
+ unless state =~ /^(Enabled|Suspended)$/
224
+ raise ArgumentError, "invalid versioning state `#{state}`"
225
+ end
226
+ super(req, options)
227
+ req.body = <<-XML.strip
228
+ <VersioningConfiguration xmlns="#{XMLNS}">
229
+ <Status>#{state}</Status>
230
+ </VersioningConfiguration>
231
+ XML
232
+ end
233
+ end
234
+
235
+ ##
236
+ # Gets the bucket's location constraint.
237
+ # @return [String] The bucket location constraint. Returns nil if
238
+ # the bucket was created in the US classic region.
239
+ bucket_method(:get_bucket_location, :get, 'location') do
240
+ process_response do |response|
241
+ regex = />(.*)<\/LocationConstraint>/
242
+ matches = response.http_response.body.match(regex)
243
+ location = matches ? matches[1] : nil
244
+ MetaUtils.extend_method(response, :location_constraint) { location }
245
+ end
246
+ end
247
+
248
+ bucket_method(:get_bucket_versioning, :get, 'versioning',
249
+ XML::GetBucketVersioning)
250
+
251
+ bucket_method(:list_object_versions, :get, 'versions',
252
+ XML::ListObjectVersions) do
253
+
254
+ configure_request do |req, options|
255
+ super(req, options)
256
+ params = %w(delimiter key_marker max_keys prefix version_id_marker)
257
+ params.each do |param|
258
+ if options[param.to_sym]
259
+ req.add_param(param.gsub(/_/, '-'), options[param.to_sym])
260
+ end
261
+ end
262
+ end
263
+
264
+ end
265
+
266
+ ##
267
+ # Sets the access control list for a bucket.
268
+ #
269
+ # == Required Options
270
+ #
271
+ # * +:bucket_name+ -- The name of the bucket.
272
+ #
273
+ # * +:acl+ -- The new acl. This can be any of the following:
274
+ # * An XML policy as a string (which is passed to S3 uninterpreted)
275
+ # * An AccessControlList object
276
+ # * Any object that responds to +to_xml+
277
+ # * Any Hash that is acceptable as an argument to
278
+ # AccessControlList#initialize.
279
+ #
280
+ # == Response
281
+ #
282
+ # The response contains only the standard fields.
283
+ bucket_method(:set_bucket_acl, :put, 'acl') do
284
+ configure_request do |req, options|
285
+ require_acl!(options[:acl])
286
+ super(req, options)
287
+ if options[:acl].kind_of?(Hash)
288
+ req.body = AccessControlList.new(options[:acl]).to_xml
289
+ elsif options[:acl].respond_to?(:to_str)
290
+ req.body = options[:acl]
291
+ else
292
+ req.body = options[:acl].to_xml
293
+ end
294
+ end
295
+ end
296
+
297
+ ##
298
+ # Gets the access control list for a bucket.
299
+ #
300
+ # == Required Options
301
+ #
302
+ # * +:bucket_name+ -- The name of the bucket.
303
+ #
304
+ # == Response
305
+ #
306
+ # A successful response will have an +acl+ method that
307
+ # returns an instance of AccessControlList.
308
+ #
309
+ bucket_method(:get_bucket_acl, :get, 'acl',
310
+ XML::GetBucketAcl)
311
+
312
+ ##
313
+ # Sets the access control list for an object.
314
+ #
315
+ # == Required Options
316
+ #
317
+ # * +:bucket_name+ -- The name of the bucket.
318
+ #
319
+ # * +:key+ -- The key of the object.
320
+ #
321
+ # * +:acl+ -- The new acl. This can be a string (which is
322
+ # assumed to contain a valid ACL expressed in XML), a
323
+ # AccessControlList or any object whose +to_xml+ returns a
324
+ # valid ACL expressed in XML.
325
+ #
326
+ # == Response
327
+ #
328
+ # The response contains only the standard fields.
329
+ object_method(:set_object_acl, :put, 'acl') do
330
+ configure_request do |req, options|
331
+ require_acl!(options[:acl])
332
+ super(req, options)
333
+ if options[:acl].kind_of?(Hash)
334
+ req.body = AccessControlList.new(options[:acl]).to_xml
335
+ elsif options[:acl].respond_to?(:to_str)
336
+ req.body = options[:acl]
337
+ else
338
+ req.body = options[:acl].to_xml
339
+ end
340
+ end
341
+ end
342
+
343
+ ##
344
+ # Gets the access control list for an object.
345
+ #
346
+ # == Required Options
347
+ #
348
+ # * +:bucket_name+ -- The name of the bucket.
349
+ #
350
+ # * +:key+ -- The key of the object.
351
+ #
352
+ # == Response
353
+ #
354
+ # A successful response will have an +acl+ method that
355
+ # returns an instance of AccessControlList.
356
+ object_method(:get_object_acl, :get, 'acl',
357
+ XML::GetBucketAcl)
358
+
359
+ ##
360
+ # Puts data into an object, replacing the current contents.
361
+ #
362
+ # == Required Options
363
+ #
364
+ # * +:bucket_name+ -- The name of the bucket that will contain the data.
365
+ #
366
+ # * +:key+ -- The key under which the data will be saved.
367
+ #
368
+ # * +:data+ -- The data to upload. This can be provided as an option
369
+ # or when using block form (see below). Valid values include:
370
+ #
371
+ # * A string
372
+ #
373
+ # * A Pathname object.
374
+ #
375
+ # * Any object with +read+ and +eof?+ methods that behave
376
+ # like Ruby's IO class (e.g. IO, File, Tempfile, StringIO, etc).
377
+ # The object must support the following access methods:
378
+ #
379
+ # read # all at once
380
+ # read(length) until eof? # in chunks
381
+ #
382
+ # == Optional
383
+ #
384
+ # * +:content_length+ -- Required if you are using block form to
385
+ # write data or if it is not possible to determine the size of
386
+ # +:data+. Best effort is made to determine the content length of
387
+ # strings, files, tempfiles, io objects, and any object that responds
388
+ # to #length or #size.
389
+ #
390
+ # * +:metadata+ -- A hash of metadata to be included with the
391
+ # object. These will be sent to S3 as headers prefixed with
392
+ # +x-amz-meta+.
393
+ #
394
+ # * +:acl+ -- A canned access control policy, valid values are:
395
+ # * +:private+
396
+ # * +:public_read+
397
+ # * ...
398
+ # Defaults to +:private+
399
+ #
400
+ # * +:storage_class+ -- Controls whether Reduced Redundancy
401
+ # Storage is enabled for the object. Valid values are
402
+ # +:standard+ (the default) or +:reduced_redundancy+
403
+ #
404
+ # * +:cache_control+ -- Can be used to specify caching
405
+ # behavior [...]
406
+ #
407
+ # * +:content_disposition+ -- Specifies presentational
408
+ # information [...]
409
+ #
410
+ # * +:content_encoding+ -- Specifies what content encodings
411
+ # have been [...]
412
+ #
413
+ # * +:content_md5+ -- The base64 encoded 128-bit [...]
414
+ #
415
+ # * +:content_type+ -- A standard MIME type describing [...]
416
+ #
417
+ # == Block Form
418
+ #
419
+ # In block form, this method yields a stream to the block that
420
+ # accepts data chunks. For example:
421
+ #
422
+ # s3_client.put_object(
423
+ # :bucket_name => 'mybucket',
424
+ # :key => 'some/key'
425
+ # :content_length => File.size('myfile')
426
+ # ) do |buffer|
427
+ #
428
+ # File.open('myfile') do |io|
429
+ # buffer.write(io.read(length)) until io.eof?
430
+ # end
431
+ #
432
+ # end
433
+ #
434
+ # This form is useful if you need finer control over how
435
+ # potentially large amounts of data are read from another
436
+ # source before being sent to S3; for example, if you are
437
+ # using a non-blocking IO model and reading from a large file
438
+ # on disk or from another network stream. Some HTTP handlers
439
+ # do not support streaming request bodies, so if you plan to
440
+ # upload large objects using this interface you should make
441
+ # sure the HTTP handler you configure for the client meets
442
+ # your needs.
443
+ #
444
+ # == Response
445
+ #
446
+ # If bucket versioning is enabled, a successful response will
447
+ # have a +version_id+ method that returns the version ID of
448
+ # the version that was written in the request.
449
+ #
450
+ object_method(:put_object, :put,
451
+ :header_options => {
452
+ :content_md5 => 'Content-MD5',
453
+ :cache_control => 'Cache-Control',
454
+ :content_disposition => 'Content-Disposition',
455
+ :content_encoding => 'Content-Encoding',
456
+ :content_type => 'Content-Type',
457
+ :storage_class => 'x-amz-storage-class'
458
+ }) do
459
+ configure_request do |request, options, block|
460
+ super(request, options)
461
+ set_request_data(request, options, block)
462
+ request.metadata = options[:metadata]
463
+ request.canned_acl = options[:acl]
464
+ request.storage_class = options[:storage_class]
465
+ end
466
+
467
+ process_response do |response|
468
+ MetaUtils.extend_method(response, :version_id) do
469
+ response.http_response.header('x-amz-version-id')
470
+ end
471
+ MetaUtils.extend_method(response, :etag) do
472
+ response.http_response.header('ETag')
473
+ end
474
+ end
475
+
476
+ simulate_response do |response|
477
+ MetaUtils.extend_method(response, :etag) { "abc123" }
478
+ MetaUtils.extend_method(response, :version_id) { nil }
479
+ end
480
+ end
481
+
482
+ ##
483
+ # Gets the data for a key.
484
+ #
485
+ # == Required Options
486
+ #
487
+ # * +:bucket_name+ -- The name of the bucket that contains the data.
488
+ #
489
+ # * +:key+ -- The key under which the data exists.
490
+ #
491
+ # == Optional
492
+ #
493
+ # * +:if_modified_since+ -- A Time object; if specified, the
494
+ # response will contain an additional +modified?+ method that
495
+ # returns true if the object was modified after the given
496
+ # time. If +modified?+ returns false, the +data+ method of
497
+ # the response will return +nil+.
498
+ #
499
+ # * +:if_unmodified_since+ -- A Time object; if specified, the
500
+ # response will contain an additional +unmodified?+ method
501
+ # that returns true if the object was not modified after the
502
+ # given time. If +unmodified?+ returns false, the +data+
503
+ # method of the response will return +nil+.
504
+ #
505
+ # * +:if_match+ -- A string; if specified, the response will
506
+ # contain an additional +matches?+ method that returns true
507
+ # if the object ETag matches the value for this option. If
508
+ # +matches?+ returns false, the +data+ method of the
509
+ # response will return +nil+.
510
+ #
511
+ # * +:if_none_match+ -- A string; if specified, the response
512
+ # will contain an additional +matches?+ method that returns
513
+ # true if and only if the object ETag matches the value for
514
+ # this option. If +matches?+ returns true, the +data+
515
+ # method of the response will return +nil+.
516
+ #
517
+ # * +:to+ -- A destination for the data. Valid values:
518
+ #
519
+ # * The path to a file as a string
520
+ #
521
+ # * A Pathname object
522
+ #
523
+ # * Any object that supports <code>write(data)</code> and
524
+ # +close+ methods like Ruby's IO class
525
+ #
526
+ # * +:range+ -- TODO: figure out the format for this
527
+ # parameter.
528
+ #
529
+ # == Response
530
+ #
531
+ # A successful response will have some combination of the
532
+ # following methods:
533
+ #
534
+ # * +data+ -- The object data as a string. This will return
535
+ # +nil+ if one of the conditional options above is specified
536
+ # and the condition is not met. It will also return +nil+
537
+ # if +deleted?+ returns true. It will not be present if the
538
+ # +:to+ option is specified.
539
+ #
540
+ # * +modified?+, +unmodified?+, +matches?+ -- These will be
541
+ # present as documented in the conditional options above.
542
+ #
543
+ # * +version_id+ -- Returns the version ID of the object that
544
+ # was written (only for versioned buckets).
545
+ #
546
+ # * +deleted?+ -- This will return +true+ if the bucket has
547
+ # versioning enabled and the object retrieved was a delete
548
+ # marker.
549
+ object_method(:get_object, :get,
550
+ :header_options => {
551
+ :range => "Range",
552
+ :if_modified_since => "If-Modified-Since",
553
+ :if_unmodified_since => "If-Unmodified-Since",
554
+ :if_match => "If-Match",
555
+ :if_none_match => "If-None-Match"
556
+ }) do
557
+ configure_request do |req, options|
558
+ super(req, options)
559
+ if options[:version_id]
560
+ req.add_param('versionId', options[:version_id])
561
+ end
562
+ ["If-Modified-Since",
563
+ "If-Unmodified-Since"].each do |date_header|
564
+ case value = req.headers[date_header]
565
+ when DateTime
566
+ req.headers[date_header] = Time.parse(value.to_s).rfc2822
567
+ when Time
568
+ req.headers[date_header] = value.rfc2822
569
+ end
570
+ end
571
+ end
572
+
573
+ process_response do |resp|
574
+ MetaUtils.extend_method(resp, :data) { resp.http_response.body }
575
+ MetaUtils.extend_method(resp, :version_id) do
576
+ http_response.header('x-amz-version-id')
577
+ end
578
+ end
579
+ end
580
+
581
+ object_method(:head_object, :head) do
582
+ configure_request do |req, options|
583
+ super(req, options)
584
+ if options[:version_id]
585
+ req.add_param('versionId', options[:version_id])
586
+ end
587
+ end
588
+
589
+ process_response do |resp|
590
+
591
+ # create a hash of user-supplied metadata
592
+ MetaUtils.extend_method(resp, :meta) do
593
+ meta = {}
594
+ resp.http_response.headers.each_pair do |name,value|
595
+ if name =~ /^x-amz-meta-(.+)$/i
596
+ meta[$1] = [value].flatten.join
597
+ end
598
+ end
599
+ meta
600
+ end
601
+
602
+ # create methods for standard response headers
603
+ {
604
+ 'x-amz-version-id' => :version_id,
605
+ 'content-type' => :content_type,
606
+ 'etag' => :etag,
607
+ }.each_pair do |header,method|
608
+ MetaUtils.extend_method(resp, method) do
609
+ http_response.header(header)
610
+ end
611
+ end
612
+
613
+ MetaUtils.extend_method(resp, :content_length) do
614
+ http_response.header('content-length').to_i
615
+ end
616
+ end
617
+ end
618
+
619
+ object_method(:delete_object, :delete) do
620
+ configure_request do |req, options|
621
+ super(req, options)
622
+ if options[:version_id]
623
+ req.add_param('versionId', options[:version_id])
624
+ end
625
+ end
626
+
627
+ process_response do |resp|
628
+ MetaUtils.extend_method(resp, :version_id) do
629
+ http_response.header('x-amz-version-id')
630
+ end
631
+ end
632
+
633
+ end
634
+
635
+ bucket_method(:list_objects, :get, XML::ListObjects) do
636
+ configure_request do |req, options|
637
+ super(req, options)
638
+ params = %w(delimiter marker max_keys prefix)
639
+ params.each do |param|
640
+ if options[param.to_sym]
641
+ req.add_param(param.gsub(/_/, '-'), options[param.to_sym])
642
+ end
643
+ end
644
+ end
645
+ end
646
+
647
+ object_method(:initiate_multipart_upload, :post, 'uploads',
648
+ XML::InitiateMultipartUpload,
649
+ :header_options => {
650
+ :cache_control => 'Cache-Control',
651
+ :content_disposition => 'Content-Disposition',
652
+ :content_encoding => 'Content-Encoding',
653
+ :content_type => 'Content-Type',
654
+ :storage_class => 'x-amz-storage-class'
655
+ }) do
656
+ configure_request do |req, options|
657
+ super(req, options)
658
+ req.metadata = options[:metadata]
659
+ req.canned_acl = options[:acl]
660
+ req.storage_class = options[:storage_class]
661
+ end
662
+ end
663
+
664
+ bucket_method(:list_multipart_uploads,
665
+ :get, 'uploads',
666
+ XML::ListMultipartUploads) do
667
+ configure_request do |req, options|
668
+ super(req, options)
669
+ params = %w(delimiter key_marker max_keys) +
670
+ %w(upload_id_marker max_uploads prefix)
671
+ params.each do |param|
672
+ if options[param.to_sym]
673
+ req.add_param(param.gsub(/_/, '-'), options[param.to_sym])
674
+ end
675
+ end
676
+ end
677
+ end
678
+
679
+ object_method(:upload_part, :put,
680
+ :header_options => {
681
+ :content_md5 => 'Content-MD5'
682
+ }) do
683
+ configure_request do |request, options, block|
684
+ require_upload_id!(options[:upload_id])
685
+ validate!("part_number", options[:part_number]) do
686
+ "must not be blank" if options[:part_number].to_s.empty?
687
+ end
688
+ super(request, options)
689
+ set_request_data(request, options, block)
690
+ request.add_param('uploadId', options[:upload_id])
691
+ request.add_param('partNumber', options[:part_number])
692
+ end
693
+
694
+ process_response do |response|
695
+ MetaUtils.extend_method(response, :etag) do
696
+ response.http_response.header('ETag')
697
+ end
698
+ end
699
+
700
+ simulate_response do |response|
701
+ MetaUtils.extend_method(response, :etag) { "abc123" }
702
+ end
703
+ end
704
+
705
+ object_method(:complete_multipart_upload, :post,
706
+ XML::CompleteMultipartUpload) do
707
+ configure_request do |req, options|
708
+ require_upload_id!(options[:upload_id])
709
+ validate_parts!(options[:parts])
710
+ super(req, options)
711
+ req.add_param('uploadId', options[:upload_id])
712
+ parts_xml = options[:parts].map do |part|
713
+ "<Part>"+
714
+ "<PartNumber>#{part[:part_number].to_i}</PartNumber>"+
715
+ "<ETag>#{REXML::Text.normalize(part[:etag].to_s)}</ETag>"+
716
+ "</Part>"
717
+ end.join
718
+ req.body =
719
+ "<CompleteMultipartUpload>#{parts_xml}</CompleteMultipartUpload>"
720
+ end
721
+
722
+ process_response do |response|
723
+ MetaUtils.extend_method(response, :version_id) do
724
+ response.http_response.header('x-amz-version-id')
725
+ end
726
+ end
727
+
728
+ simulate_response do |response|
729
+ MetaUtils.extend_method(response, :version_id) { nil }
730
+ end
731
+ end
732
+
733
+ object_method(:abort_multipart_upload, :delete) do
734
+ configure_request do |req, options|
735
+ require_upload_id!(options[:upload_id])
736
+ super(req, options)
737
+ req.add_param('uploadId', options[:upload_id])
738
+ end
739
+ end
740
+
741
+ object_method(:list_parts, :get,
742
+ XML::ListParts) do
743
+ configure_request do |req, options|
744
+ require_upload_id!(options[:upload_id])
745
+ super(req, options)
746
+ req.add_param('uploadId', options[:upload_id])
747
+ req.add_param('max-parts', options[:max_parts])
748
+ req.add_param('part-number-marker', options[:part_number_marker])
749
+ end
750
+ end
751
+
752
+ ##
753
+ # @param [Hash] options
754
+ # @option options [required, String] :bucket_name Name of the bucket
755
+ # to copy a object into.
756
+ # @option options [required, String] :key Where (object key) in the
757
+ # bucket the object should be copied to.
758
+ # @option options [required, String] :copy_source The name of the
759
+ # source bucket and key name of the source object, separated by a
760
+ # slash (/). This string must be URL-encoded. Additionally, the
761
+ # source bucket must be valid and you must have READ access to
762
+ # the valid source object.
763
+ # @option options [Symbol] :acl
764
+ #
765
+ object_method(:copy_object, :put,
766
+ :header_options => {
767
+ :copy_source => 'x-amz-copy-source',
768
+ :metadata_directive => 'x-amz-metadata-directive',
769
+ :storage_class => 'x-amz-storage-class',
770
+ }
771
+ ) do
772
+
773
+ configure_request do |req, options|
774
+ # TODO : validate presence of copy source
775
+ # TODO : validate metadata directive COPY / REPLACE
776
+ # TODO : validate storage class STANDARD / REDUCED_REDUNDANCY
777
+ # TODO : add validations for storage class in other places used
778
+ super(req, options)
779
+ req.canned_acl = options[:acl]
780
+ req.metadata = options[:metadata]
781
+ req.storage_class = options[:storage_class]
782
+ if options[:version_id]
783
+ req.headers['x-amz-copy-source'] += "?versionId=#{options[:version_id]}"
784
+ end
785
+ end
786
+
787
+ process_response do |response|
788
+ MetaUtils.extend_method(response, :version_id) do
789
+ response.http_response.header('x-amz-version-id')
790
+ end
791
+ MetaUtils.extend_method(response, :etag) do
792
+ response.http_response.header('ETag')
793
+ end
794
+ end
795
+
796
+ end
797
+
798
+ protected
799
+ def xml_error_response? response
800
+ (response.http_response.status >= 300 ||
801
+ response.request_type == :complete_multipart_upload) and
802
+ XmlGrammar.parse(response.http_response.body).respond_to?(:code)
803
+ end
804
+
805
+ protected
806
+ def should_retry? response
807
+ super or
808
+ response.request_type == :complete_multipart_upload &&
809
+ xml_error_response?(response)
810
+ end
811
+
812
+ protected
813
+ def set_request_data request, options, block
814
+ request.body_stream = data_stream_from(options, &block)
815
+ request.headers['Content-Length'] = content_length_from(options)
816
+ end
817
+
818
+ protected
819
+ def new_request
820
+ S3::Request.new
821
+ end
822
+
823
+ module Validators
824
+
825
+ # Returns true if the given bucket name is valid.
826
+ def valid_bucket_name?(bucket_name)
827
+ validate_bucket_name!(bucket_name) rescue false
828
+ end
829
+
830
+ def dns_compatible_bucket_name?(bucket_name)
831
+ return false if
832
+ !valid_bucket_name?(bucket_name) or
833
+
834
+ # Bucket names should not contain underscores (_)
835
+ bucket_name["_"] or
836
+
837
+ # Bucket names should be between 3 and 63 characters long
838
+ bucket_name.size > 63 or
839
+
840
+ # Bucket names should not end with a dash
841
+ bucket_name[-1,1] == '-' or
842
+
843
+ # Bucket names cannot contain two, adjacent periods
844
+ bucket_name['..'] or
845
+
846
+ # Bucket names cannot contain dashes next to periods
847
+ # (e.g., "my-.bucket.com" and "my.-bucket" are invalid)
848
+ (bucket_name['-.'] || bucket_name['.-'])
849
+
850
+ true
851
+ end
852
+
853
+ protected
854
+ def validate! name, value, &block
855
+ if error_msg = yield
856
+ raise ArgumentError, "#{name} #{error_msg}"
857
+ end
858
+ value
859
+ end
860
+
861
+ protected
862
+ def validate_key!(key)
863
+ validate!('key', key) do
864
+ case
865
+ when key.nil? || key == ''
866
+ 'may not be blank'
867
+ end
868
+ end
869
+ end
870
+
871
+ protected
872
+ def require_bucket_name! bucket_name
873
+ if [nil, ''].include?(bucket_name)
874
+ raise ArgumentError, "bucket_name may not be blank"
875
+ end
876
+ end
877
+
878
+
879
+ # Returns true if the given bucket name is valid. If the name
880
+ # is invalid, an ArgumentError is raised.
881
+ protected
882
+ def validate_bucket_name!(bucket_name)
883
+ validate!('bucket_name', bucket_name) do
884
+ case
885
+ when bucket_name.nil? || bucket_name == ''
886
+ 'may not be blank'
887
+ when bucket_name !~ /^[a-z0-9._\-]+$/
888
+ 'may only contain lowercase letters, numbers, periods (.), ' +
889
+ 'underscores (_), and dashes (-)'
890
+ when bucket_name !~ /^[a-z0-9]/
891
+ 'must start with a letter or a number'
892
+ when !(3..255).include?(bucket_name.size)
893
+ 'must be between 3 and 255 characters long'
894
+ when bucket_name =~ /(\d+\.){3}\d+/
895
+ 'must not be formatted like an IP address (e.g., 192.168.5.4)'
896
+ when bucket_name =~ /\n/
897
+ 'must not contain a newline character'
898
+ end
899
+ end
900
+ end
901
+
902
+ protected
903
+ def require_policy!(policy)
904
+ validate!('policy', policy) do
905
+ case
906
+ when policy.nil? || policy == ''
907
+ 'may not be blank'
908
+ else
909
+ json_validation_message(policy)
910
+ end
911
+ end
912
+ end
913
+
914
+ protected
915
+ def require_acl!(acl)
916
+ validate!('acl', acl) do
917
+ case
918
+ when acl.kind_of?(Hash)
919
+ AccessControlList.new(acl).validate!
920
+ nil
921
+ when !acl.respond_to?(:to_str) && !acl.respond_to?(:to_xml)
922
+ "must support to_xml: #{acl.inspect}"
923
+ when acl.nil? || acl == ''
924
+ 'may not be blank'
925
+ else
926
+ xml_validation_message(acl)
927
+ end
928
+ end
929
+ end
930
+
931
+ protected
932
+ def require_upload_id!(upload_id)
933
+ validate!("upload_id", upload_id) do
934
+ "must not be blank" if upload_id.to_s.empty?
935
+ end
936
+ end
937
+
938
+ protected
939
+ def validate_parts!(parts)
940
+ validate!("parts", parts) do
941
+ if !parts.kind_of?(Array)
942
+ "must not be blank"
943
+ elsif parts.empty?
944
+ "must contain at least one entry"
945
+ elsif !parts.all? { |p| p.kind_of?(Hash) }
946
+ "must be an array of hashes"
947
+ elsif !parts.all? { |p| p[:part_number] }
948
+ "must contain part_number for each part"
949
+ elsif !parts.all? { |p| p[:etag] }
950
+ "must contain etag for each part"
951
+ elsif parts.any? { |p| p[:part_number].to_i < 1 }
952
+ "must not have part numbers less than 1"
953
+ end
954
+ end
955
+ end
956
+
957
+ protected
958
+ def json_validation_message(obj)
959
+ if obj.respond_to?(:to_str)
960
+ obj = obj.to_str
961
+ elsif obj.respond_to?(:to_json)
962
+ obj = obj.to_json
963
+ end
964
+
965
+ error = nil
966
+ begin
967
+ JSON.parse(obj)
968
+ rescue => e
969
+ error = e
970
+ end
971
+ "contains invalid JSON: #{error}" if error
972
+ end
973
+
974
+ protected
975
+ def xml_validation_message(obj)
976
+ if obj.respond_to?(:to_str)
977
+ obj = obj.to_str
978
+ elsif obj.respond_to?(:to_xml)
979
+ obj = obj.to_xml
980
+ end
981
+
982
+ error = nil
983
+ begin
984
+ REXML::Document.new(obj)
985
+ rescue => e
986
+ error = e
987
+ end
988
+ "contains invalid XML: #{error}" if error
989
+ end
990
+
991
+ end
992
+
993
+ include Validators
994
+ extend Validators
995
+
996
+ end
997
+
998
+ end
999
+ end