diff --git a/Rakefile b/Rakefile index 8ef9010c..968bd31e 100644 --- a/Rakefile +++ b/Rakefile @@ -16,7 +16,7 @@ end desc "Run the test_server" task :test_server do |t| - system("bundle exec bin/fakes3 --port 10453 --root test_root") + system("bundle exec bin/fakes3 --port 10453 --root test_root --corspostputallowheaders 'Authorization, Content-Length, Cache-Control'") end task :default => :test diff --git a/lib/fakes3/cli.rb b/lib/fakes3/cli.rb index 3f5a2cb3..5886fd82 100644 --- a/lib/fakes3/cli.rb +++ b/lib/fakes3/cli.rb @@ -15,6 +15,11 @@ class CLI < Thor method_option :limit, :aliases => '-l', :type => :string, :desc => 'Rate limit for serving (ie. 50K, 1.0M)' method_option :sslcert, :type => :string, :desc => 'Path to SSL certificate' method_option :sslkey, :type => :string, :desc => 'Path to SSL certificate key' + method_option :corsorigin, :type => :string, :desc => 'Access-Control-Allow-Origin header return value' + method_option :corsmethods, :type => :string, :desc => 'Access-Control-Allow-Methods header return value' + method_option :corspreflightallowheaders, :type => :string, :desc => 'Access-Control-Allow-Headers header return value for preflight OPTIONS requests' + method_option :corspostputallowheaders, :type => :string, :desc => 'Access-Control-Allow-Headers header return value for POST and PUT requests' + method_option :corsexposeheaders, :type => :string, :desc => 'Access-Control-Expose-Headers header return value' def server store = nil @@ -45,6 +50,13 @@ def server end end + cors_options = {} + cors_options['allow_origin'] = options[:corsorigin] if options[:corsorigin] + cors_options['allow_methods'] = options[:corsmethods] if options[:corsmethods] + cors_options['preflight_allow_headers'] = options[:corspreflightallowheaders] if options[:corspreflightallowheaders] + cors_options['post_put_allow_headers'] = options[:corspostputallowheaders] if options[:corspostputallowheaders] + cors_options['expose_headers'] = options[:corsexposeheaders] if options[:corsexposeheaders] + address = options[:address] ssl_cert_path = options[:sslcert] ssl_key_path = options[:sslkey] @@ -54,7 +66,7 @@ def server end puts "Loading FakeS3 with #{root} on port #{options[:port]} with hostname #{hostname}" unless options[:quiet] - server = FakeS3::Server.new(address,options[:port],store,hostname,ssl_cert_path,ssl_key_path, quiet: !!options[:quiet]) + server = FakeS3::Server.new(address,options[:port],store,hostname,ssl_cert_path,ssl_key_path, quiet: !!options[:quiet], cors_options: cors_options) server.serve end diff --git a/lib/fakes3/server.rb b/lib/fakes3/server.rb index 9a23cc2d..1167dec1 100644 --- a/lib/fakes3/server.rb +++ b/lib/fakes3/server.rb @@ -49,12 +49,19 @@ def inspect end class Servlet < WEBrick::HTTPServlet::AbstractServlet - def initialize(server,store,hostname) + def initialize(server,store,hostname,cors_options) super(server) @store = store @hostname = hostname @port = server.config[:Port] @root_hostnames = [hostname,'localhost','s3.amazonaws.com','s3.localhost'] + + # Here lies hard-coded defaults for CORS Configuration + @cors_allow_origin = (cors_options['allow_origin'] or '*') + @cors_allow_methods = (cors_options['allow_methods'] or 'PUT, POST, HEAD, GET, OPTIONS') + @cors_preflight_allow_headers = (cors_options['preflight_allow_headers'] or 'Accept, Content-Type, Authorization, Content-Length, ETag, X-CSRF-Token, Content-Disposition') + @cors_post_put_allow_headers = (cors_options['post_put_allow_headers'] or 'Authorization, Content-Length') + @cors_expose_headers = (cors_options['expose_headers'] or 'ETag') end def validate_request(request) @@ -101,7 +108,7 @@ def do_GET(request, response) response.status = 404 response.body = XmlAdapter.error_no_such_key(s_req.object) response['Content-Type'] = "application/xml" - response['Access-Control-Allow-Origin'] = '*' + response['Access-Control-Allow-Origin'] = @cors_allow_origin return end @@ -134,7 +141,7 @@ def do_GET(request, response) response.header['ETag'] = "\"#{real_obj.md5}\"" response['Accept-Ranges'] = "bytes" response['Last-Ranges'] = "bytes" - response['Access-Control-Allow-Origin'] = '*' + response['Access-Control-Allow-Origin'] = @cors_allow_origin real_obj.custom_metadata.each do |header, value| response.header['x-amz-meta-' + header] = value @@ -188,7 +195,7 @@ def do_PUT(request, response) response.status = 200 response.body = "" response['Content-Type'] = "text/xml" - response['Access-Control-Allow-Origin'] = '*' + response['Access-Control-Allow-Origin'] = @cors_allow_origin case s_req.type when Request::COPY @@ -240,9 +247,9 @@ def do_multipartPUT(request, response) response.header['ETag'] = "\"#{real_obj.md5}\"" end - response['Access-Control-Allow-Origin'] = '*' - response['Access-Control-Allow-Headers'] = 'Authorization, Content-Length' - response['Access-Control-Expose-Headers'] = 'ETag' + response['Access-Control-Allow-Origin'] = @cors_allow_origin + response['Access-Control-Allow-Headers'] = @cors_post_put_allow_headers + response['Access-Control-Expose-Headers'] = @cors_expose_headers response.status = 200 end @@ -322,9 +329,9 @@ def do_POST(request,response) end response['Content-Type'] = 'text/xml' - response['Access-Control-Allow-Origin'] = '*' - response['Access-Control-Allow-Headers'] = 'Authorization, Content-Length' - response['Access-Control-Expose-Headers'] = 'ETag' + response['Access-Control-Allow-Origin'] = @cors_allow_origin + response['Access-Control-Allow-Headers'] = @cors_post_put_allow_headers + response['Access-Control-Expose-Headers'] = @cors_expose_headers end def do_DELETE(request, response) @@ -348,10 +355,10 @@ def do_DELETE(request, response) def do_OPTIONS(request, response) super - response['Access-Control-Allow-Origin'] = '*' - response['Access-Control-Allow-Methods'] = 'PUT, POST, HEAD, GET, OPTIONS' - response['Access-Control-Allow-Headers'] = 'Accept, Content-Type, Authorization, Content-Length, ETag, X-CSRF-Token, Content-Disposition' - response['Access-Control-Expose-Headers'] = 'ETag' + response['Access-Control-Allow-Origin'] = @cors_allow_origin + response['Access-Control-Allow-Methods'] = @cors_allow_methods + response['Access-Control-Allow-Headers'] = @cors_preflight_allow_headers + response['Access-Control-Expose-Headers'] = @cors_expose_headers end private @@ -550,6 +557,7 @@ def initialize(address, port, store, hostname, ssl_cert_path, ssl_key_path, extr @hostname = hostname @ssl_cert_path = ssl_cert_path @ssl_key_path = ssl_key_path + @cors_options = extra_options[:cors_options] or {} webrick_config = { :BindAddress => @address, :Port => @port @@ -575,7 +583,7 @@ def initialize(address, port, store, hostname, ssl_cert_path, ssl_key_path, extr end def serve - @server.mount "/", Servlet, @store, @hostname + @server.mount "/", Servlet, @store, @hostname, @cors_options shutdown = proc { @server.shutdown } trap "INT", &shutdown trap "TERM", &shutdown diff --git a/test/post_test.rb b/test/post_test.rb index 7e82eb62..045656db 100644 --- a/test/post_test.rb +++ b/test/post_test.rb @@ -29,6 +29,8 @@ def test_redirect ) { |response| assert_equal(response.code, 303) assert_equal(response.headers[:location], 'http://somewhere.else.com/?foo=bar&bucket=posttest&key=uploads%2F12345%2Fpost_test.rb') + # Tests that CORS Headers can be set from command line + assert_equal(response.headers[:access_control_allow_headers], 'Authorization, Content-Length, Cache-Control') } end @@ -40,6 +42,8 @@ def test_status_200 'file'=>File.new(__FILE__,"rb") ) { |response| assert_equal(response.code, 200) + # Tests that CORS Headers can be set from command line + assert_equal(response.headers[:access_control_allow_headers], 'Authorization, Content-Length, Cache-Control') } end @@ -52,6 +56,8 @@ def test_status_201 ) { |response| assert_equal(response.code, 201) assert_match(%r{^\<\?xml.*uploads/12345/post_test\.rb}m, response.body) + # Tests that CORS Headers can be set from command line + assert_equal(response.headers[:access_control_allow_headers], 'Authorization, Content-Length, Cache-Control') } end