Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Rack::CORS middleware #59

Closed
wants to merge 1 commit into from

4 participants

@RafeKettler

Attached is some very simple middleware to allow Cross Origin Resource Sharing for a flexible list of domains. The list of domains that is passed as an argument to the middleware can contain the same kind of wildcards that shell globs can (underneath the hood, I use File.fnmatch? to check Origins against patterns).

@rkh rkh commented on the diff
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 Owner
rkh added a note

Could you not use a class variable here?

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 Collaborator
jjb added a note

@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
@mpalmer
Owner

domain_patterns should be an instance variable, which can be fixed up on merge. Other than that, anything that makes CORS easier to deal with gets my vote. I'm especially keen on the presence of test cases.

@mpalmer mpalmer added needs-work and removed needs-work labels
@jjb
Collaborator
jjb commented

might be interesting to compare to the popular font_assets gem https://github.com/ericallam/font_assets/blob/master/lib/font_assets/middleware.rb

@jjb
Collaborator
jjb commented

@ericallam could we get your feedback on this PR?

notably, this PR doesn't set

"Access-Control-Allow-Methods" => "GET",
"Access-Control-Allow-Headers" => "x-requested-with",
"Access-Control-Max-Age" => "3628800"

and maybe there are other best practices.

@mpalmer mpalmer added this to the 1.3 milestone
@mpalmer mpalmer self-assigned this
@mpalmer
Owner

LGTM.

@mpalmer mpalmer removed the needs-cleanup label
@jjb
Collaborator

There are two very popular CORS gems, https://github.com/cyu/rack-cors and https://github.com/ericallam/font_assets. We should avoid being redundant with these.

rack-cors is actively developed. Does this PR do anything that rack-cors doesn't do? Maybe when this PR was submitted in 2012 rack-cors wasn't sufficient.

I would vote to close this and not merge it unless @RafeKettler or someone else knows that this code does something that rack-cors does not do (and even if that's the case, perhaps it would be more appropriate as a patch to rack-cors).

@mpalmer
Owner

Good call, @jjb. I wasn't aware of the other available options.

I'll leave this open another couple of days, and if nobody comes up with a reason why this PR is needed, I'll drop it.

@mpalmer mpalmer removed this from the 1.3 milestone
@mpalmer
Owner

Given no objections, and having looked at the other available options and found them quite tasty, I'm closing this PR.

@mpalmer mpalmer closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 9, 2012
  1. @RafeKettler

    Add Rack::CORS, middleware for flexibly enabling Cross Origin Resourc…

    RafeKettler authored
    …e Sharing for a list of domains or domain patterns
This page is out of date. Refresh to see the latest.
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>
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 Owner
rkh added a note

Could you not use a class variable here?

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 Collaborator
jjb added a note

@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.