Skip to content

Rack::CORS middleware #59

Closed
wants to merge 1 commit into from
View
1 AUTHORS
@@ -24,3 +24,4 @@ TJ Holowaychuk <tj@vision-media.ca>
anupom syam <anupom.syam@gmail.com>
ichverstehe <ichverstehe@gmail.com>
kubicek <jiri@kubicek.cz>
+Rafe Kettler <rafe.kettler@gmail.com>
View
1 README.rdoc
@@ -48,6 +48,7 @@ interface:
* Rack::ResponseHeaders - Manipulates response headers object at runtime
* Rack::SimpleEndpoint - Creates simple endpoints with routing rules, similar to Sinatra actions
* Rack::TryStatic - Tries to match request to a static file
+* Rack::CORS - Allows for flexible enabling of Cross Origin Resource Sharing (CORS) for a specific set of domains
=== Use
View
1 lib/rack/contrib.rb
@@ -11,6 +11,7 @@ def self.release
autoload :Access, "rack/contrib/access"
autoload :BounceFavicon, "rack/contrib/bounce_favicon"
autoload :Cookies, "rack/contrib/cookies"
+ autoload :CORS, "rack/contrib/cors"
autoload :CSSHTTPRequest, "rack/contrib/csshttprequest"
autoload :Deflect, "rack/contrib/deflect"
autoload :ExpectationCascade, "rack/contrib/expectation_cascade"
View
25 lib/rack/contrib/cors.rb
@@ -0,0 +1,25 @@
+module Rack
+ class CORS
+ # Enables Cross Origin Resource Sharing for a list of domains (or domain
+ # patterns). Example inputs:
+ # ['*'] -- allow all cross domain requests
+ # ['http://mysite.com', 'http://localhost:*'] -- allow all requests from mysite and
+ # any port on localhost
+ # ['http://*.mysite.com'] -- allow all requests from any subdomain on localhost
+ def initialize(app, domain_patterns = [])
+ @app = app
+ @@domain_patterns = domain_patterns
@rkh
Official Rack repositories member
rkh added a note Dec 10, 2012

Could you not use a class variable here?

@RafeKettler
RafeKettler added a note Dec 13, 2012

Yes. In practice, they'd be no different. I don't actually remember why I had it written this way in the first place, though. Feel free to change it

@jjb
jjb added a note Nov 2, 2014

@RafeKettler please rewrite this with an instance variable, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ end
+
+ def call(env)
+ status, headers, body = @app.call(env)
+ # Check the list of domain patterns to see if any match our Origin header.
+ # If so, set Access-Control-Allow-Origin to the request's Origin
+ origin = env['HTTP_ORIGIN']
+ if origin && @@domain_patterns.any? { |pattern| ::File.fnmatch?(pattern, origin) }
+ headers['Access-Control-Allow-Origin'] = origin
+ end
+ [status, headers, body]
+ end
+ end
+end
View
1 rack-contrib.gemspec
@@ -27,6 +27,7 @@ Gem::Specification.new do |s|
lib/rack/contrib/common_cookies.rb
lib/rack/contrib/config.rb
lib/rack/contrib/cookies.rb
+ lib/rack/contrib/cors.rb
lib/rack/contrib/csshttprequest.rb
lib/rack/contrib/deflect.rb
lib/rack/contrib/evil.rb
View
70 test/spec_rack_cors.rb
@@ -0,0 +1,70 @@
+require 'test/spec'
+require 'rack/mock'
+require 'rack/contrib/cors'
+
+context "Rack::CORS" do
+ setup do
+ @app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, env['PATH_INFO']] }
+ @no_domains = []
+ @allow_all = ['*']
+ @specific = ['http://site.com']
+ @wildcards = ['http://localhost:*', 'http://*.site.com']
+ @all_together_now = @wildcards.concat @specific
+ @site_origin = 'http://site.com'
+ @site_port = 'http://site.com:8080'
+ @site_subdomain = 'http://news.site.com'
+ @localhost_plain = 'http://localhost'
+ @localhost_rails = 'http://localhost:3000'
+ @all_origins = [@site_origin, @site_subdomain, @localhost_rails]
+ end
+
+ def mock_origin(origin)
+ mock = Rack::MockRequest.env_for('/', { 'HTTP_ORIGIN' => origin })
+ end
+
+ def middleware(domain_patterns)
+ Rack::CORS.new(@app, domain_patterns)
+ end
+
+ specify 'specifying no domains should never set an Access-Control-Allow-Origin' do
+ app = middleware(@no_domains)
+ @all_origins.each do |origin|
+ status, headers, body = app.call(mock_origin(origin))
+ headers['Access-Control-Allow-Origin'].should == nil
+ end
+ end
+
+ specify 'all domains should set Access-Control-Allow-Origin to Origin always' do
+ app = middleware(@allow_all)
+ @all_origins.each do |origin|
+ status, headers, body = app.call(mock_origin(origin))
+ headers['Access-Control-Allow-Origin'].should == origin
+ end
+ end
+
+ specify 'specific (non-globbed) domain patterns should only match one port and subdomain' do
+ app = middleware(@specific)
+ status, headers, body = app.call(mock_origin(@site_origin))
+ headers['Access-Control-Allow-Origin'].should == @site_origin
+ status, headers, body = app.call(mock_origin(@site_subdomain))
+ headers['Access-Control-Allow-Origin'].should == nil
+ status, headers, body = app.call(mock_origin(@site_port))
+ headers['Access-Control-Allow-Origin'].should == nil
+ end
+
+ specify 'ports and subdomains should be globbable' do
+ app = middleware(@wildcards)
+ status, headers, body = app.call(mock_origin(@localhost_rails))
+ headers['Access-Control-Allow-Origin'].should == @localhost_rails
+ status, headers, body = app.call(mock_origin(@site_subdomain))
+ headers['Access-Control-Allow-Origin'].should == @site_subdomain
+ end
+
+ specify 'all types of patterns should mix well' do
+ app = middleware(@all_together_now)
+ @all_origins.each do |origin|
+ status, headers, body = app.call(mock_origin(origin))
+ headers['Access-Control-Allow-Origin'].should == origin
+ end
+ end
+end
Something went wrong with that request. Please try again.