mss-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 (131) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +9 -0
  3. data/LICENSE.txt +0 -0
  4. data/README.md +192 -0
  5. data/bin/mss-rb +178 -0
  6. data/ca-bundle.crt +3554 -0
  7. data/lib/mss/core/async_handle.rb +89 -0
  8. data/lib/mss/core/cacheable.rb +76 -0
  9. data/lib/mss/core/client.rb +786 -0
  10. data/lib/mss/core/collection/simple.rb +81 -0
  11. data/lib/mss/core/collection/with_limit_and_next_token.rb +70 -0
  12. data/lib/mss/core/collection/with_next_token.rb +96 -0
  13. data/lib/mss/core/collection.rb +262 -0
  14. data/lib/mss/core/configuration.rb +527 -0
  15. data/lib/mss/core/credential_providers.rb +653 -0
  16. data/lib/mss/core/data.rb +251 -0
  17. data/lib/mss/core/deprecations.rb +83 -0
  18. data/lib/mss/core/endpoints.rb +36 -0
  19. data/lib/mss/core/http/connection_pool.rb +374 -0
  20. data/lib/mss/core/http/curb_handler.rb +150 -0
  21. data/lib/mss/core/http/handler.rb +88 -0
  22. data/lib/mss/core/http/net_http_handler.rb +144 -0
  23. data/lib/mss/core/http/patch.rb +98 -0
  24. data/lib/mss/core/http/request.rb +258 -0
  25. data/lib/mss/core/http/response.rb +80 -0
  26. data/lib/mss/core/indifferent_hash.rb +87 -0
  27. data/lib/mss/core/inflection.rb +55 -0
  28. data/lib/mss/core/ini_parser.rb +41 -0
  29. data/lib/mss/core/json_client.rb +46 -0
  30. data/lib/mss/core/json_parser.rb +75 -0
  31. data/lib/mss/core/json_request_builder.rb +34 -0
  32. data/lib/mss/core/json_response_parser.rb +78 -0
  33. data/lib/mss/core/lazy_error_classes.rb +107 -0
  34. data/lib/mss/core/log_formatter.rb +426 -0
  35. data/lib/mss/core/managed_file.rb +31 -0
  36. data/lib/mss/core/meta_utils.rb +44 -0
  37. data/lib/mss/core/model.rb +61 -0
  38. data/lib/mss/core/naming.rb +29 -0
  39. data/lib/mss/core/option_grammar.rb +737 -0
  40. data/lib/mss/core/options/json_serializer.rb +81 -0
  41. data/lib/mss/core/options/validator.rb +154 -0
  42. data/lib/mss/core/options/xml_serializer.rb +117 -0
  43. data/lib/mss/core/page_result.rb +74 -0
  44. data/lib/mss/core/policy.rb +938 -0
  45. data/lib/mss/core/query_client.rb +40 -0
  46. data/lib/mss/core/query_error_parser.rb +23 -0
  47. data/lib/mss/core/query_request_builder.rb +46 -0
  48. data/lib/mss/core/query_response_parser.rb +34 -0
  49. data/lib/mss/core/region.rb +84 -0
  50. data/lib/mss/core/region_collection.rb +79 -0
  51. data/lib/mss/core/resource.rb +412 -0
  52. data/lib/mss/core/resource_cache.rb +39 -0
  53. data/lib/mss/core/response.rb +214 -0
  54. data/lib/mss/core/response_cache.rb +49 -0
  55. data/lib/mss/core/rest_error_parser.rb +23 -0
  56. data/lib/mss/core/rest_json_client.rb +39 -0
  57. data/lib/mss/core/rest_request_builder.rb +153 -0
  58. data/lib/mss/core/rest_response_parser.rb +65 -0
  59. data/lib/mss/core/rest_xml_client.rb +46 -0
  60. data/lib/mss/core/service_interface.rb +82 -0
  61. data/lib/mss/core/signers/base.rb +45 -0
  62. data/lib/mss/core/signers/cloud_front.rb +55 -0
  63. data/lib/mss/core/signers/s3.rb +158 -0
  64. data/lib/mss/core/signers/version_2.rb +71 -0
  65. data/lib/mss/core/signers/version_3.rb +85 -0
  66. data/lib/mss/core/signers/version_3_https.rb +60 -0
  67. data/lib/mss/core/signers/version_4/chunk_signed_stream.rb +190 -0
  68. data/lib/mss/core/signers/version_4.rb +227 -0
  69. data/lib/mss/core/uri_escape.rb +43 -0
  70. data/lib/mss/core/xml/frame.rb +245 -0
  71. data/lib/mss/core/xml/frame_stack.rb +84 -0
  72. data/lib/mss/core/xml/grammar.rb +306 -0
  73. data/lib/mss/core/xml/parser.rb +69 -0
  74. data/lib/mss/core/xml/root_frame.rb +64 -0
  75. data/lib/mss/core/xml/sax_handlers/libxml.rb +46 -0
  76. data/lib/mss/core/xml/sax_handlers/nokogiri.rb +55 -0
  77. data/lib/mss/core/xml/sax_handlers/ox.rb +40 -0
  78. data/lib/mss/core/xml/sax_handlers/rexml.rb +46 -0
  79. data/lib/mss/core/xml/stub.rb +122 -0
  80. data/lib/mss/core.rb +602 -0
  81. data/lib/mss/errors.rb +161 -0
  82. data/lib/mss/rails.rb +194 -0
  83. data/lib/mss/s3/access_control_list.rb +262 -0
  84. data/lib/mss/s3/acl_object.rb +263 -0
  85. data/lib/mss/s3/acl_options.rb +200 -0
  86. data/lib/mss/s3/bucket.rb +757 -0
  87. data/lib/mss/s3/bucket_collection.rb +161 -0
  88. data/lib/mss/s3/bucket_lifecycle_configuration.rb +472 -0
  89. data/lib/mss/s3/bucket_region_cache.rb +51 -0
  90. data/lib/mss/s3/bucket_tag_collection.rb +110 -0
  91. data/lib/mss/s3/bucket_version_collection.rb +78 -0
  92. data/lib/mss/s3/cipher_io.rb +119 -0
  93. data/lib/mss/s3/client/xml.rb +265 -0
  94. data/lib/mss/s3/client.rb +2076 -0
  95. data/lib/mss/s3/config.rb +60 -0
  96. data/lib/mss/s3/cors_rule.rb +107 -0
  97. data/lib/mss/s3/cors_rule_collection.rb +193 -0
  98. data/lib/mss/s3/data_options.rb +190 -0
  99. data/lib/mss/s3/encryption_utils.rb +145 -0
  100. data/lib/mss/s3/errors.rb +93 -0
  101. data/lib/mss/s3/multipart_upload.rb +353 -0
  102. data/lib/mss/s3/multipart_upload_collection.rb +75 -0
  103. data/lib/mss/s3/object_collection.rb +355 -0
  104. data/lib/mss/s3/object_metadata.rb +102 -0
  105. data/lib/mss/s3/object_upload_collection.rb +76 -0
  106. data/lib/mss/s3/object_version.rb +153 -0
  107. data/lib/mss/s3/object_version_collection.rb +88 -0
  108. data/lib/mss/s3/paginated_collection.rb +74 -0
  109. data/lib/mss/s3/policy.rb +73 -0
  110. data/lib/mss/s3/prefix_and_delimiter_collection.rb +46 -0
  111. data/lib/mss/s3/prefixed_collection.rb +84 -0
  112. data/lib/mss/s3/presign_v4.rb +135 -0
  113. data/lib/mss/s3/presigned_post.rb +574 -0
  114. data/lib/mss/s3/region_detection.rb +75 -0
  115. data/lib/mss/s3/request.rb +61 -0
  116. data/lib/mss/s3/s3_object.rb +1795 -0
  117. data/lib/mss/s3/tree/branch_node.rb +67 -0
  118. data/lib/mss/s3/tree/child_collection.rb +103 -0
  119. data/lib/mss/s3/tree/leaf_node.rb +93 -0
  120. data/lib/mss/s3/tree/node.rb +21 -0
  121. data/lib/mss/s3/tree/parent.rb +86 -0
  122. data/lib/mss/s3/tree.rb +115 -0
  123. data/lib/mss/s3/uploaded_part.rb +81 -0
  124. data/lib/mss/s3/uploaded_part_collection.rb +83 -0
  125. data/lib/mss/s3/website_configuration.rb +101 -0
  126. data/lib/mss/s3.rb +161 -0
  127. data/lib/mss/version.rb +16 -0
  128. data/lib/mss-sdk.rb +2 -0
  129. data/lib/mss.rb +14 -0
  130. data/rails/init.rb +14 -0
  131. metadata +201 -0
@@ -0,0 +1,60 @@
1
+ # Copyright 2011-2013 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
+ #
8
+ # or in the "license" file accompanying this file. This file is
9
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
10
+ # ANY KIND, either express or implied. See the License for the specific
11
+ # language governing permissions and limitations under the License.
12
+
13
+ MSS::Core::Configuration.module_eval do
14
+
15
+ add_service 'S3', 's3', 's3'
16
+
17
+ add_option :s3_force_path_style, false, :boolean => true
18
+
19
+ add_option :s3_multipart_threshold, 16 * 1024 * 1024
20
+
21
+ add_option :s3_multipart_min_part_size, 5 * 1024 * 1024
22
+
23
+ add_option :s3_multipart_max_parts, 10000
24
+
25
+ add_option :s3_server_side_encryption, nil
26
+
27
+ add_option :s3_encryption_key, nil
28
+
29
+ add_option :s3_encryption_materials_location, :metadata
30
+
31
+ add_option :s3_encryption_matdesc, '{}'
32
+
33
+ add_option :s3_storage_class, 'STANDARD'
34
+
35
+ add_option :s3_cache_object_attributes, false, :boolean => true
36
+
37
+ add_option :s3_signature_version do |config, value|
38
+ v3_regions = %w(
39
+ us-east-1
40
+ us-west-1
41
+ us-west-2
42
+ ap-northeast-1
43
+ ap-southeast-1
44
+ ap-southeast-2
45
+ sa-east-1
46
+ eu-west-1
47
+ us-gov-west-1
48
+ )
49
+ if value
50
+ value
51
+ elsif config.s3 && config.s3[:signature_version]
52
+ config.s3[:signature_version]
53
+ elsif v3_regions.include?(config.s3_region)
54
+ :v3
55
+ else
56
+ :v4
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,107 @@
1
+ # Copyright 2011-2013 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
+ #
8
+ # or in the "license" file accompanying this file. This file is
9
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
10
+ # ANY KIND, either express or implied. See the License for the specific
11
+ # language governing permissions and limitations under the License.
12
+
13
+ module MSS
14
+ class S3
15
+
16
+ # Represents a single CORS rule for an S3 {Bucket}.
17
+ #
18
+ # @example
19
+ #
20
+ # rule = bucket.cors.first
21
+ # rule.allowed_methods #=> ['GET', 'HEAD']
22
+ # rule.allowed_origins #=> ['*']
23
+ #
24
+ # @see CORSRuleCollection
25
+ class CORSRule
26
+
27
+ # @param [Hash] options A hash of the rule details.
28
+ #
29
+ # @option options [String] :id A unique identifier for the rule. The ID
30
+ # value can be up to 255 characters long. The IDs help you find
31
+ # a rule in the configuration.
32
+ #
33
+ # @option options [required,Array<String>] :allowed_methods A list of HTTP
34
+ # methods that you want to allow the origin to execute.
35
+ # Each rule must identify at least one method.
36
+ #
37
+ # @option options [required,Array<String>] :allowed_origins A list of
38
+ # origins you want to allow cross-domain requests from. This can
39
+ # contain at most one * wild character.
40
+ #
41
+ # @option options [Array<String>] :allowed_headers A list of headers
42
+ # allowed in a pre-flight OPTIONS request via the
43
+ # Access-Control-Request-Headers header. Each header name
44
+ # specified in the Access-Control-Request-Headers header must
45
+ # have a corresponding entry in the rule.
46
+ #
47
+ # Amazon S3 will send only the allowed headers in a response
48
+ # that were requested. This can contain at most one '*' wild
49
+ # character.
50
+ #
51
+ # @option options [Array<String>] :max_age_seconds The time in
52
+ # seconds that your browser is to cache the pre-flight response for
53
+ # the specified resource.
54
+ #
55
+ # @option options [Array<String>] :expose_headers One or more headers in
56
+ # the response that you want customers to be able to access
57
+ # from their applications (for example, from a JavaScript
58
+ # XMLHttpRequest object).
59
+ #
60
+ def initialize options = {}
61
+ @id = options[:id]
62
+ @allowed_methods = options[:allowed_methods] || []
63
+ @allowed_origins = options[:allowed_origins] || []
64
+ @allowed_headers = options[:allowed_headers] || []
65
+ @max_age_seconds = options[:max_age_seconds]
66
+ @expose_headers = options[:expose_headers] || []
67
+ end
68
+
69
+ # @return [String,nil] A user supplied unique identifier for this role.
70
+ # Set this when you set or add roles via {CORSRuleCollection}.
71
+ attr_reader :id
72
+
73
+ # @return [Array<String>] A list of HTTP methods (GET, POST, etc) this
74
+ # role authorizes.
75
+ attr_reader :allowed_methods
76
+
77
+ # @return [Array<String>] The list of origins allowed to make
78
+ # cross-domain requests to the bucket.
79
+ attr_reader :allowed_origins
80
+
81
+ # @return [Array<String>] A list of headers allowed in the pre-flight
82
+ # OPTIONS request.
83
+ attr_reader :allowed_headers
84
+
85
+ # @return [Integer,nil] The time in seconds the browser may cache the
86
+ # pre-flight response.
87
+ attr_reader :max_age_seconds
88
+
89
+ # @return [Array<String>] The headers that may be accessed cross-domain.
90
+ attr_reader :expose_headers
91
+
92
+ # @return [Hash]
93
+ def to_h
94
+ h = {}
95
+ h[:id] = id if id
96
+ h[:allowed_methods] = allowed_methods
97
+ h[:allowed_origins] = allowed_origins
98
+ h[:allowed_headers] = allowed_headers unless allowed_headers.empty?
99
+ h[:max_age_seconds] = max_age_seconds if max_age_seconds
100
+ h[:expose_headers] = expose_headers unless expose_headers.empty?
101
+ h
102
+ end
103
+
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,193 @@
1
+ # Copyright 2011-2013 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
+ #
8
+ # or in the "license" file accompanying this file. This file is
9
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
10
+ # ANY KIND, either express or implied. See the License for the specific
11
+ # language governing permissions and limitations under the License.
12
+
13
+ module MSS
14
+ class S3
15
+
16
+ # Manages the CORS rules for a single bucket.
17
+ #
18
+ # ## Getting Rules
19
+ #
20
+ # To get the CORS rules for a bucket, use the {Bucket#cors} method. This
21
+ # returns a CORSRuleCollection for the bucket. The collection is
22
+ # enumerable.
23
+ #
24
+ # # enumerating all rules for a buck
25
+ # bucket.cors.each do |rule|
26
+ # # rule is a CORSRule object
27
+ # end
28
+ #
29
+ # ## Setting Rules
30
+ #
31
+ # You can set the rules for a bucket (replacing all existing rules) using
32
+ # the {#set} method.
33
+ #
34
+ # # accepts a list of one or more rules
35
+ # bucket.rules.set(rule1, rule2)
36
+ #
37
+ # # rules can be an array of rules
38
+ # bucket.rules.set(rules)
39
+ #
40
+ # # passing an empty list or array removes all rules
41
+ # bucket.rules.set([])
42
+ # bucket.rules.clear # does the same thing
43
+ #
44
+ # Each rule can be a Hash, a {CORSRule} or another {CORSRuleCollection}.
45
+ # See {Client#put_bucket_cors} for a list of keys for a rule hash.
46
+ #
47
+ # ## Adding Rules
48
+ #
49
+ # Adding rules is the same as setting rules. Rules you add will be
50
+ # appended to the end of the existing list of rules.
51
+ #
52
+ # # add one or more rules
53
+ # bucket.rules.add(rules)
54
+ #
55
+ # ## Deleting Rules
56
+ #
57
+ # To remove a rule, use the {#delete_if} method.
58
+ #
59
+ # # delete rules that allow access from any domain
60
+ # bucket.cors.delete_if{|rule| rule.allowed_origins.include?('*')
61
+ class CORSRuleCollection
62
+
63
+ include Core::Collection::Simple
64
+
65
+ # @param [Bucket] bucket
66
+ # @param [Hash] options
67
+ def initialize bucket, options = {}
68
+ @bucket = bucket
69
+ super
70
+ end
71
+
72
+ # @return [Bucket]
73
+ attr_reader :bucket
74
+
75
+ # Replaces the CORS rules attached to this bucket. You can pass
76
+ # one or more rules as an array or a list.
77
+ #
78
+ # # replace all exisitng rules with a single rule
79
+ # bucket.cors.set(
80
+ # :allowed_methods => %w(GET),
81
+ # :allowed_origins => %w(http://*.mydomain.com),
82
+ # :max_age_seconds => 3600)
83
+ #
84
+ # If you pass an empty array, all of the rules will be removed from
85
+ # the bucket.
86
+ #
87
+ # # these two lines are equivilent
88
+ # bucket.cors.clear
89
+ # bucket.cors.set([])
90
+ #
91
+ # @param [Hash,CORSRule,CORSRuleCollection] rules A list or array
92
+ # of one or more rules to set. Each rule may be a Hash, a CORSRule
93
+ # or a CORSRuleCollection.
94
+ #
95
+ # @return [nil]
96
+ #
97
+ def set *rules
98
+
99
+ raise ArgumentError, 'expected one or more rules' if rules.empty?
100
+
101
+ if rules == [[]]
102
+ self.clear
103
+ else
104
+ rules = rule_hashes(rules)
105
+ client.put_bucket_cors(:bucket_name => bucket.name, :rules => rules)
106
+ end
107
+
108
+ nil
109
+
110
+ end
111
+
112
+ # Add one or more CORS rules to this bucket.
113
+ #
114
+ # # adding a single rule as a hash
115
+ # bucket.cors.add(
116
+ # :allowed_methods => %w(GET HEAD),
117
+ # :allowed_origins => %w(*),
118
+ # :max_age_seconds => 3600)
119
+ #
120
+ # You can add multiple rules in a single call:
121
+ #
122
+ # # each rule may be a hash, CORSRule or a CORSRuleCollection,
123
+ # bucket.cors.add(rules)
124
+ #
125
+ # # alternatively you can pass a list of rules
126
+ # bucket.cors.add(rule1, rule2, ...)
127
+ #
128
+ # @param (see #set)
129
+ # @return (see #set)
130
+ def add *rules
131
+ self.set(self, *rules)
132
+ end
133
+ alias_method :create, :add
134
+
135
+ # Deletes every rule for which the block evaluates to `true`.
136
+ #
137
+ # @example Remove all rules that are open to the 'world'
138
+ #
139
+ # bucket.cors.delete_if{|rule| rule.allowed_origins.include?('*') }
140
+ #
141
+ # @yield [rule]
142
+ # @yieldparam [CORSRule] rule
143
+ # @yieldreturn [Boolean] Return `true` for each rule you want to delete.
144
+ # @return (see #set)
145
+ def delete_if &block
146
+ rules = []
147
+ self.each do |rule|
148
+ rules << rule unless yield(rule)
149
+ end
150
+ self.set(rules)
151
+ end
152
+
153
+ # Removes all CORS rules attached to this bucket.
154
+ #
155
+ # @example
156
+ #
157
+ # bucket.cors.count #=> 3
158
+ # bucket.cors.clear
159
+ # bucket.cors.count #=> 0
160
+ #
161
+ # @return [nil]
162
+ def clear
163
+ client.delete_bucket_cors(:bucket_name => bucket.name)
164
+ nil
165
+ end
166
+
167
+ protected
168
+
169
+ def _each_item options
170
+ resp = client.get_bucket_cors(options.merge(:bucket_name => bucket.name))
171
+ resp.data[:rules].each do |rule|
172
+ yield(CORSRule.new(rule))
173
+ end
174
+ rescue MSS::S3::Errors::NoSuchCORSConfiguration
175
+ # no cors rules exist for this bucket, nothing to yield
176
+ end
177
+
178
+ def rule_hashes rule
179
+ case rule
180
+ when Hash then rule
181
+ when CORSRule then rule.to_h
182
+ when CORSRuleCollection then rule.map(&:to_h)
183
+ when Array then rule.map{|r| rule_hashes(r) }.flatten
184
+ else
185
+ msg = "Expected one or more CORSRule, CORSRuleCollection or hash"
186
+ msg << ", got #{rule.class.name}"
187
+ raise ArgumentError, msg
188
+ end
189
+ end
190
+
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,190 @@
1
+ # Copyright 2011-2013 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
+ #
8
+ # or in the "license" file accompanying this file. This file is
9
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
10
+ # ANY KIND, either express or implied. See the License for the specific
11
+ # language governing permissions and limitations under the License.
12
+
13
+ require 'pathname'
14
+
15
+ module MSS
16
+ class S3
17
+
18
+ # Used by S3#S3Object and S3::Client to accept options with
19
+ # data that should be uploaded (streamed).
20
+ # @api private
21
+ module DataOptions
22
+
23
+ protected
24
+
25
+ # @return [Hash] Returns a hash of options with a :data option that
26
+ # responds to #read and #eof?.
27
+ def compute_write_options *args, &block
28
+
29
+ options = convert_args_to_options_hash(*args)
30
+
31
+ validate_data!(options, &block)
32
+
33
+ rename_file_to_data(options)
34
+
35
+ convert_data_to_io_obj(options, &block)
36
+
37
+ try_to_determine_content_length(options)
38
+
39
+ options
40
+
41
+ end
42
+
43
+ # Converts an argument list into a single hash of options. Treats
44
+ # non-hash arguments in the first position as a data option.
45
+ def convert_args_to_options_hash *args
46
+ case args.count
47
+ when 0 then {}
48
+ when 1 then args[0].is_a?(Hash) ? args[0] : { :data => args[0] }
49
+ when 2 then args[1].merge(:data => args[0])
50
+ else
51
+ msg = "expected 0, 1 or 2 arguments, got #{args.count}"
52
+ raise ArgumentError, msg
53
+ end
54
+ end
55
+
56
+ # Moves options[:file] to options[:data]. If this option is a string
57
+ # then it is treated as a file path and is converted to an open file.
58
+ def rename_file_to_data options
59
+ if file = options.delete(:file)
60
+ options[:data] = file.is_a?(String) ? open_file(file) : file
61
+ end
62
+ end
63
+
64
+ # Converts the :data option to an IO-like object. This allows us
65
+ # to always perform streaming uploads.
66
+ def convert_data_to_io_obj options, &block
67
+
68
+ data = options.delete(:data)
69
+
70
+ if block_given?
71
+ options[:data] = IOProxy.new(block)
72
+ elsif data.is_a?(String)
73
+ data = data.dup if data.frozen?
74
+ data.force_encoding("BINARY") if data.respond_to?(:force_encoding)
75
+ options[:data] = StringIO.new(data)
76
+ elsif data.is_a?(Pathname)
77
+ options[:data] = open_file(data.to_s)
78
+ elsif io_like?(data)
79
+ options[:data] = data
80
+ else
81
+ msg = "invalid :data option, expected a String, Pathname or "
82
+ msg << "an object that responds to #read and #eof?"
83
+ raise ArgumentError, msg
84
+ end
85
+
86
+ end
87
+
88
+ # Attempts to determine the content length of the :data option.
89
+ # This is only done when a content length is not already provided.
90
+ def try_to_determine_content_length options
91
+ unless options[:content_length]
92
+
93
+ data = options[:data]
94
+
95
+ length = case
96
+ when data.respond_to?(:path) && data.path then File.size(data.path)
97
+ when data.respond_to?(:bytesize) then data.bytesize
98
+ when data.respond_to?(:size) then data.size
99
+ when data.respond_to?(:length) then data.length
100
+ else nil
101
+ end
102
+
103
+ options[:content_length] = length if length
104
+
105
+ end
106
+ end
107
+
108
+ def validate_data! options, &block
109
+
110
+ data = options[:data]
111
+ file = options[:file]
112
+
113
+ raise ArgumentError, 'Object data passed multiple ways.' if
114
+ [data, file, block].compact.count > 1
115
+
116
+ data = file if file
117
+
118
+ return if block_given?
119
+ return if data.kind_of?(String)
120
+ return if data.kind_of?(Pathname)
121
+ return if io_like?(data)
122
+
123
+ msg = ":data must be provided as a String, Pathname, File, or "
124
+ msg << "an object that responds to #read and #eof?"
125
+ raise ArgumentError, msg
126
+
127
+ end
128
+
129
+ # @return [Boolean] Returns `true` if the object responds to
130
+ # `#read` and `#eof?`.
131
+ def io_like? io
132
+ io.respond_to?(:read) and io.respond_to?(:eof?)
133
+ end
134
+
135
+ # @param [String] path Path to a file on disk.
136
+ # @return [File] Given a path string, returns an open File.
137
+ def open_file path
138
+ Core::ManagedFile.open(path)
139
+ end
140
+
141
+ # A utility class that turns a block (with 2 args) into an
142
+ # IO object that responds to #read and #eof.
143
+ # @api private
144
+ class IOProxy
145
+
146
+ def initialize write_block
147
+ unless write_block.arity == 2
148
+ msg = "a write block must accept 2 yield params: a buffer and "
149
+ msg << "a number of bytes to write"
150
+ raise ArgumentError, msg
151
+ end
152
+ @write_block = write_block
153
+ @eof = false
154
+ end
155
+
156
+ def read bytes = nil, output_buffer = nil
157
+ data = if bytes
158
+ (@eof) ? nil : read_chunk(bytes)
159
+ else
160
+ (@eof) ? "" : read_all
161
+ end
162
+ output_buffer ? output_buffer.replace(data || '') : data
163
+ end
164
+
165
+ def eof?
166
+ @eof
167
+ end
168
+
169
+ protected
170
+
171
+ def read_chunk bytes
172
+ buffer = StringIO.new
173
+ @write_block.call(buffer, bytes)
174
+ buffer.rewind
175
+ @eof = true if buffer.size < bytes
176
+ buffer.read
177
+ end
178
+
179
+ def read_all
180
+ buffer = StringIO.new
181
+ buffer << read_chunk(1024 * 1024 * 5) until @eof
182
+ buffer.rewind
183
+ buffer.read
184
+ end
185
+
186
+ end
187
+
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,145 @@
1
+ # Copyright 2011-2013 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
+ #
8
+ # or in the "license" file accompanying this file. This file is
9
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
10
+ # ANY KIND, either express or implied. See the License for the specific
11
+ # language governing permissions and limitations under the License.
12
+
13
+ require 'openssl'
14
+
15
+ module MSS
16
+ class S3
17
+ # @api private
18
+ module EncryptionUtils
19
+
20
+ protected
21
+
22
+ UNSAFE_MSG = "Unsafe encryption, data is longer than key length"
23
+
24
+ # @param [OpenSSL::PKey::RSA, String] key Key used to encrypt.
25
+ #
26
+ # @param [String] data Data to be encrypted.
27
+ #
28
+ # @note Use check_encryption_materials before this method to check
29
+ # formatting of keys.
30
+ # @note This should not be used for data longer than the key length as
31
+ # it will not be cryptographically safe.
32
+ #
33
+ # @return [String] Returns the data encrypted with the key given.
34
+ def encrypt data, key
35
+ rsa = OpenSSL::PKey::RSA
36
+ data_cipher_size = get_cipher_size(data.length)
37
+
38
+ # Encrypting data key
39
+ case key
40
+ when rsa # Asymmetric encryption
41
+ warn UNSAFE_MSG if key.public_key.n.num_bits < data_cipher_size
42
+ key.public_encrypt(data)
43
+ when String # Symmetric encryption
44
+ warn UNSAFE_MSG if get_cipher_size(key.length) < data_cipher_size
45
+ cipher = get_aes_cipher(:encrypt, :ECB, key)
46
+ cipher.update(data) + cipher.final
47
+ end
48
+ end
49
+
50
+ # @param [OpenSSL::PKey::RSA, String] key Key used to encrypt.
51
+ #
52
+ # @param [String] data Data to be encrypted.
53
+ #
54
+ # @note Use check_encryption_materials before this method to check
55
+ # formatting of keys
56
+ #
57
+ # @return [String] Returns the data decrypted with the key given.
58
+ def decrypt data, key
59
+ rsa = OpenSSL::PKey::RSA
60
+ begin
61
+ case key
62
+ when rsa # Asymmetric Decryption
63
+ key.private_decrypt(data)
64
+ when String # Symmetric Decryption
65
+ cipher = get_aes_cipher(:decrypt, :ECB, key)
66
+ cipher.update(data) + cipher.final
67
+ end
68
+ rescue OpenSSL::Cipher::CipherError
69
+ raise RuntimeError, "decryption failed, incorrect key?"
70
+ end
71
+ end
72
+
73
+ # Checks for any formatting problems for keys and initialization vectors
74
+ # supported with EncryptionUtils.
75
+ def check_encryption_materials mode, key
76
+ rsa = OpenSSL::PKey::RSA
77
+ case key
78
+ when rsa
79
+ unless key.private? or mode == :encrypt
80
+ msg = "invalid key, #{rsa} requires a private key"
81
+ raise ArgumentError, msg
82
+ end
83
+ when String # no problem
84
+ else
85
+ msg = "invalid key, must be an #{rsa} or a cipher key string"
86
+ raise ArgumentError, msg
87
+ end
88
+ end
89
+
90
+ # @param [OpenSSL::Cipher] cipher The cipher with configured key and iv.
91
+ #
92
+ # @yield [String, String] key_iv_pair A randomly generated key, iv pair
93
+ # for use with the given cipher. Sets the key and iv on the cipher.
94
+ def generate_aes_key cipher, &block
95
+ key_iv_pair = [cipher.random_key, cipher.random_iv]
96
+ yield(key_iv_pair) if block_given?
97
+ end
98
+
99
+ # @param [Symbol] mode The encryption/decryption mode. Valid inputs are
100
+ # :encrypt or :decrypt
101
+ #
102
+ # @param [String] key Key for the cipher.
103
+ #
104
+ # @param [String] iv IV for the cipher.
105
+ #
106
+ # @return [OpenSSL::Cipher] Will return a configured `OpenSSL::Cipher`.
107
+ def get_aes_cipher mode, block_mode, key = nil, iv = nil
108
+
109
+ # If no key given, default to 256 bit
110
+ cipher_size = (key) ? get_cipher_size(key.length) : 256
111
+
112
+ cipher = OpenSSL::Cipher.new("AES-#{cipher_size}-#{block_mode}")
113
+
114
+ (mode == :encrypt) ? cipher.encrypt : cipher.decrypt
115
+ cipher.key = key if key
116
+ cipher.iv = iv if iv
117
+ cipher
118
+ end
119
+
120
+ # @param [Integer] size Size of data given.
121
+ # @return [Integer] Returns the AES encrypted size based on a given size.
122
+ def get_encrypted_size size
123
+ # The next multiple of 16
124
+ ((size / 16) + 1) * 16
125
+ end
126
+ module_function :get_encrypted_size
127
+
128
+ private
129
+
130
+ # @param [Fixnum] key_length Length of the key given.
131
+ # @return [Fixnum] Returns the cipher size based on the key length.
132
+ def get_cipher_size(key_length)
133
+ case key_length
134
+ when 32 then 256
135
+ when 24 then 192
136
+ when 16 then 128
137
+ else
138
+ msg = "invalid key, symmetric key required to be 16, 24, or 32 bytes "
139
+ msg << "in length, saw length #{key_length}"
140
+ raise ArgumentError, msg
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end