diff --git a/lib/fog/aws/parsers/storage/cors_configuration.rb b/lib/fog/aws/parsers/storage/cors_configuration.rb
new file mode 100644
index 000000000..654f34f91
--- /dev/null
+++ b/lib/fog/aws/parsers/storage/cors_configuration.rb
@@ -0,0 +1,41 @@
+module Fog
+ module Parsers
+ module Storage
+ module AWS
+
+ class CorsConfiguration < Fog::Parsers::Base
+ def reset
+ @in_cors_configuration_list = false
+ @cors_rule = {}
+ @response = { 'CORSConfiguration' => [] }
+ end
+
+ def start_element(name, attrs = [])
+ super
+ if name == 'CORSConfiguration'
+ @in_cors_configuration_list = true
+ end
+ end
+
+ def end_element(name)
+ case name
+ when 'CORSConfiguration'
+ @in_cors_configuration_list = false
+ when 'CORSRule'
+ @response['CORSConfiguration'] << @cors_rule
+ @cors_rule = {}
+ when 'MaxAgeSeconds'
+ @cors_rule[name] = value.to_i
+ when 'ID'
+ @cors_rule[name] = value
+ when 'AllowedOrigin', 'AllowedMethod', 'AllowedHeader', 'ExposeHeader'
+ (@cors_rule[name] ||= []) << value
+ end
+ end
+
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/fog/aws/requests/storage/cors_utils.rb b/lib/fog/aws/requests/storage/cors_utils.rb
new file mode 100644
index 000000000..44ecc243a
--- /dev/null
+++ b/lib/fog/aws/requests/storage/cors_utils.rb
@@ -0,0 +1,41 @@
+module Fog
+ module Storage
+ class AWS
+
+ require 'fog/aws/parsers/storage/cors_configuration'
+
+ private
+
+ def self.hash_to_cors(cors)
+ data = "\n"
+
+ [cors['CORSConfiguration']].flatten.compact.each do |rule|
+ data << " \n"
+
+ ['ID', 'MaxAgeSeconds'].each do |key|
+ data << " <#{key}>#{rule[key]}#{key}>\n" if rule[key]
+ end
+
+ ['AllowedOrigin', 'AllowedMethod', 'AllowedHeader', 'ExposeHeader'].each do |key|
+ [rule[key]].flatten.compact.each do |value|
+ data << " <#{key}>#{value}#{key}>\n"
+ end
+ end
+
+ data << " \n"
+ end
+
+ data << ""
+
+ data
+ end
+
+ def self.cors_to_hash(cors_xml)
+ parser = Fog::Parsers::Storage::AWS::CorsConfiguration.new
+ Nokogiri::XML::SAX::Parser.new(parser).parse(cors_xml)
+ parser.response
+ end
+
+ end
+ end
+end
diff --git a/lib/fog/aws/requests/storage/delete_bucket_cors.rb b/lib/fog/aws/requests/storage/delete_bucket_cors.rb
new file mode 100644
index 000000000..e26c5129f
--- /dev/null
+++ b/lib/fog/aws/requests/storage/delete_bucket_cors.rb
@@ -0,0 +1,32 @@
+module Fog
+ module Storage
+ class AWS
+ class Real
+
+ # Deletes the cors configuration information set for the bucket.
+ #
+ # ==== Parameters
+ # * bucket_name<~String> - name of bucket to delete cors rules from
+ #
+ # ==== Returns
+ # * response<~Excon::Response>:
+ # * status<~Integer> - 204
+ #
+ # ==== See Also
+ # http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketDELETEcors.html
+
+ def delete_bucket_cors(bucket_name)
+ request({
+ :expects => 204,
+ :headers => {},
+ :host => "#{bucket_name}.#{@host}",
+ :method => 'DELETE',
+ :query => {'cors' => nil}
+ })
+ end
+
+ end
+
+ end
+ end
+end
diff --git a/lib/fog/aws/requests/storage/get_bucket_cors.rb b/lib/fog/aws/requests/storage/get_bucket_cors.rb
new file mode 100644
index 000000000..e4dd0517e
--- /dev/null
+++ b/lib/fog/aws/requests/storage/get_bucket_cors.rb
@@ -0,0 +1,68 @@
+module Fog
+ module Storage
+ class AWS
+ class Real
+
+ require 'fog/aws/parsers/storage/cors_configuration'
+
+ # Gets the CORS configuration for an S3 bucket
+ #
+ # ==== Parameters
+ # * bucket_name<~String> - name of bucket to get access control list for
+ #
+ # ==== Returns
+ # * response<~Excon::Response>:
+ # * body<~Hash>:
+ # * 'CORSConfiguration'<~Array>:
+ # * 'CORSRule'<~Hash>:
+ # * 'AllowedHeader'<~String> - Which headers are allowed in a pre-flight OPTIONS request through the Access-Control-Request-Headers header.
+ # * 'AllowedMethod'<~String> - Identifies an HTTP method that the domain/origin specified in the rule is allowed to execute.
+ # * 'AllowedOrigin'<~String> - One or more response headers that you want customers to be able to access from their applications (for example, from a JavaScript XMLHttpRequest object).
+ # * 'ExposeHeader'<~String> - One or more headers in the response that you want customers to be able to access from their applications (for example, from a JavaScript XMLHttpRequest object).
+ # * 'ID'<~String> - An optional unique identifier for the rule. The ID value can be up to 255 characters long. The IDs help you find a rule in the configuration.
+ # * 'MaxAgeSeconds'<~Integer> - The time in seconds that your browser is to cache the preflight response for the specified resource.
+ #
+ # ==== See Also
+ # http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGETcors.html
+
+ def get_bucket_cors(bucket_name)
+ unless bucket_name
+ raise ArgumentError.new('bucket_name is required')
+ end
+ request({
+ :expects => 200,
+ :headers => {},
+ :host => "#{bucket_name}.#{@host}",
+ :idempotent => true,
+ :method => 'GET',
+ :parser => Fog::Parsers::Storage::AWS::CorsConfiguration.new,
+ :query => {'cors' => nil}
+ })
+ end
+
+ end
+
+ class Mock # :nodoc:all
+
+ require 'fog/aws/requests/storage/cors_utils'
+
+ def get_bucket_cors(bucket_name)
+ response = Excon::Response.new
+ if cors = self.data[:cors][:bucket][bucket_name]
+ response.status = 200
+ if cors.is_a?(String)
+ response.body = Fog::Storage::AWS.cors_to_hash(cors)
+ else
+ response.body = cors
+ end
+ else
+ response.status = 404
+ raise(Excon::Errors.status_error({:expects => 200}, response))
+ end
+ response
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/fog/aws/requests/storage/put_bucket_cors.rb b/lib/fog/aws/requests/storage/put_bucket_cors.rb
new file mode 100644
index 000000000..bbad7ac1e
--- /dev/null
+++ b/lib/fog/aws/requests/storage/put_bucket_cors.rb
@@ -0,0 +1,51 @@
+module Fog
+ module Storage
+ class AWS
+ class Real
+
+ require 'fog/aws/requests/storage/cors_utils'
+
+ # Sets the cors configuration for your bucket. If the configuration exists, Amazon S3 replaces it.
+ #
+ # ==== Parameters
+ # * bucket_name<~String> - name of bucket to modify
+ # * cors<~Hash>:
+ # * CORSConfiguration<~Array>:
+ # * ID<~String>: A unique identifier for the rule.
+ # * AllowedMethod<~String>: An HTTP method that you want to allow the origin to execute.
+ # * AllowedOrigin<~String>: An origin that you want to allow cross-domain requests from.
+ # * AllowedHeader<~String>: Specifies which headers are allowed in a pre-flight OPTIONS request via the Access-Control-Request-Headers header.
+ # * MaxAgeSeconds<~String>: The time in seconds that your browser is to cache the preflight response for the specified resource.
+ # * ExposeHeader<~String>: One or more headers in the response that you want customers to be able to access from their applications.
+ #
+ # ==== See Also
+ # http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUTcors.html
+
+ def put_bucket_cors(bucket_name, cors)
+ data = Fog::Storage::AWS.hash_to_cors(cors)
+
+ headers = {}
+ headers['Content-MD5'] = Base64.encode64(Digest::MD5.digest(data)).strip
+ headers['Content-Type'] = 'application/json'
+ headers['Date'] = Fog::Time.now.to_date_header
+
+ request({
+ :body => data,
+ :expects => 200,
+ :headers => headers,
+ :host => "#{bucket_name}.#{@host}",
+ :method => 'PUT',
+ :query => {'cors' => nil}
+ })
+ end
+ end
+
+ class Mock
+ def put_bucket_cors(bucket_name, cors)
+ self.data[:cors][:bucket][bucket_name] = Fog::Storage::AWS.hash_to_cors(cors)
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/fog/aws/storage.rb b/lib/fog/aws/storage.rb
index 42e3d3583..4a5a2d70f 100644
--- a/lib/fog/aws/storage.rb
+++ b/lib/fog/aws/storage.rb
@@ -22,6 +22,7 @@ module Fog
request :complete_multipart_upload
request :copy_object
request :delete_bucket
+ request :delete_bucket_cors
request :delete_bucket_lifecycle
request :delete_bucket_policy
request :delete_bucket_website
@@ -29,6 +30,7 @@ module Fog
request :delete_multiple_objects
request :get_bucket
request :get_bucket_acl
+ request :get_bucket_cors
request :get_bucket_lifecycle
request :get_bucket_location
request :get_bucket_logging
@@ -51,6 +53,7 @@ module Fog
request :post_object_hidden_fields
request :put_bucket
request :put_bucket_acl
+ request :put_bucket_cors
request :put_bucket_lifecycle
request :put_bucket_logging
request :put_bucket_policy
@@ -338,6 +341,7 @@ DATA
for key in (params[:query] || {}).keys.sort
if %w{
acl
+ cors
delete
lifecycle
location
diff --git a/tests/aws/requests/storage/cors_utils_tests.rb b/tests/aws/requests/storage/cors_utils_tests.rb
new file mode 100644
index 000000000..c8c8847c2
--- /dev/null
+++ b/tests/aws/requests/storage/cors_utils_tests.rb
@@ -0,0 +1,108 @@
+require 'fog/aws/requests/storage/cors_utils'
+
+Shindo.tests('Fog::Storage::AWS | CORS utils', ["aws"]) do
+ tests(".hash_to_cors") do
+ tests(".hash_to_cors({}) at xpath //CORSConfiguration").returns("", "has an empty CORSConfiguration") do
+ xml = Fog::Storage::AWS.hash_to_cors({})
+ Nokogiri::XML(xml).xpath("//CORSConfiguration").first.content.chomp
+ end
+
+ tests(".hash_to_cors({}) at xpath //CORSConfiguration/CORSRule").returns(nil, "has no CORSRules") do
+ xml = Fog::Storage::AWS.hash_to_cors({})
+ Nokogiri::XML(xml).xpath("//CORSConfiguration/CORSRule").first
+ end
+
+ cors = {
+ 'CORSConfiguration' => [
+ {
+ 'AllowedOrigin' => ['origin_123', 'origin_456'],
+ 'AllowedMethod' => ['GET', 'POST'],
+ 'AllowedHeader' => ['Accept', 'Content-Type'],
+ 'ID' => 'blah-888',
+ 'MaxAgeSeconds' => 2500,
+ 'ExposeHeader' => ['x-some-header', 'x-other-header']
+ }
+ ]
+ }
+
+ tests(".hash_to_cors(#{cors.inspect}) at xpath //CORSConfiguration/CORSRule/AllowedOrigin").returns("origin_123", "returns the CORSRule AllowedOrigin") do
+ xml = Fog::Storage::AWS.hash_to_cors(cors)
+ Nokogiri::XML(xml).xpath("//CORSConfiguration/CORSRule/AllowedOrigin")[0].content
+ end
+
+ tests(".hash_to_cors(#{cors.inspect}) at xpath //CORSConfiguration/CORSRule/AllowedOrigin").returns("origin_456", "returns the CORSRule AllowedOrigin") do
+ xml = Fog::Storage::AWS.hash_to_cors(cors)
+ Nokogiri::XML(xml).xpath("//CORSConfiguration/CORSRule/AllowedOrigin")[1].content
+ end
+
+ tests(".hash_to_cors(#{cors.inspect}) at xpath //CORSConfiguration/CORSRule/AllowedMethod").returns("GET", "returns the CORSRule AllowedMethod") do
+ xml = Fog::Storage::AWS.hash_to_cors(cors)
+ Nokogiri::XML(xml).xpath("//CORSConfiguration/CORSRule/AllowedMethod")[0].content
+ end
+
+ tests(".hash_to_cors(#{cors.inspect}) at xpath //CORSConfiguration/CORSRule/AllowedMethod").returns("POST", "returns the CORSRule AllowedMethod") do
+ xml = Fog::Storage::AWS.hash_to_cors(cors)
+ Nokogiri::XML(xml).xpath("//CORSConfiguration/CORSRule/AllowedMethod")[1].content
+ end
+
+ tests(".hash_to_cors(#{cors.inspect}) at xpath //CORSConfiguration/CORSRule/AllowedHeader").returns("Accept", "returns the CORSRule AllowedHeader") do
+ xml = Fog::Storage::AWS.hash_to_cors(cors)
+ Nokogiri::XML(xml).xpath("//CORSConfiguration/CORSRule/AllowedHeader")[0].content
+ end
+
+ tests(".hash_to_cors(#{cors.inspect}) at xpath //CORSConfiguration/CORSRule/AllowedHeader").returns("Content-Type", "returns the CORSRule AllowedHeader") do
+ xml = Fog::Storage::AWS.hash_to_cors(cors)
+ Nokogiri::XML(xml).xpath("//CORSConfiguration/CORSRule/AllowedHeader")[1].content
+ end
+
+ tests(".hash_to_cors(#{cors.inspect}) at xpath //CORSConfiguration/CORSRule/ID").returns("blah-888", "returns the CORSRule ID") do
+ xml = Fog::Storage::AWS.hash_to_cors(cors)
+ Nokogiri::XML(xml).xpath("//CORSConfiguration/CORSRule/ID")[0].content
+ end
+
+ tests(".hash_to_cors(#{cors.inspect}) at xpath //CORSConfiguration/CORSRule/MaxAgeSeconds").returns("2500", "returns the CORSRule MaxAgeSeconds") do
+ xml = Fog::Storage::AWS.hash_to_cors(cors)
+ Nokogiri::XML(xml).xpath("//CORSConfiguration/CORSRule/MaxAgeSeconds")[0].content
+ end
+
+ tests(".hash_to_cors(#{cors.inspect}) at xpath //CORSConfiguration/CORSRule/ExposeHeader").returns("x-some-header", "returns the CORSRule ExposeHeader") do
+ xml = Fog::Storage::AWS.hash_to_cors(cors)
+ Nokogiri::XML(xml).xpath("//CORSConfiguration/CORSRule/ExposeHeader")[0].content
+ end
+
+ tests(".hash_to_cors(#{cors.inspect}) at xpath //CORSConfiguration/CORSRule/ExposeHeader").returns("x-other-header", "returns the CORSRule ExposeHeader") do
+ xml = Fog::Storage::AWS.hash_to_cors(cors)
+ Nokogiri::XML(xml).xpath("//CORSConfiguration/CORSRule/ExposeHeader")[1].content
+ end
+ end
+
+ tests(".cors_to_hash") do
+ cors_xml = <<-XML
+
+
+ http://www.example.com
+ http://www.example2.com
+ Content-Length
+ X-Foobar
+ PUT
+ GET
+ 3000
+ x-amz-server-side-encryption
+ x-amz-balls
+
+
+XML
+
+ tests(".cors_to_hash(#{cors_xml.inspect})").returns({
+ "CORSConfiguration" => [{
+ "AllowedOrigin" => ["http://www.example.com", "http://www.example2.com"],
+ "AllowedHeader" => ["Content-Length", "X-Foobar"],
+ "AllowedMethod" => ["PUT", "GET"],
+ "MaxAgeSeconds" => 3000,
+ "ExposeHeader" => ["x-amz-server-side-encryption", "x-amz-balls"]
+ }]
+ }, 'returns hash of CORS XML') do
+ Fog::Storage::AWS.cors_to_hash(cors_xml)
+ end
+ end
+end