Skip to content

Commit

Permalink
more robust config; resource path matching
Browse files Browse the repository at this point in the history
  • Loading branch information
cyu committed Jun 3, 2010
1 parent 1ff3ff9 commit e6d33ff
Showing 1 changed file with 89 additions and 46 deletions.
135 changes: 89 additions & 46 deletions lib/rack/cors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ def initialize(app)
yield self if block_given?
end

def allow(origin, path, opts={})
origins << Origin.new(origin, path, opts)
def allow
all_resources << (resources = Resources.new)
yield resources
end

def call(env)
cors_headers = nil
if env['HTTP_ORIGIN']
if env['REQUEST_METHOD'] == 'OPTIONS'
headers = process_preflight(env)
headers = process_preflight(env)
return [200, headers, []] if headers
end
cors_headers = process_cors(env)
Expand All @@ -24,64 +25,106 @@ def call(env)
[status, headers, body]
end

def origins
@origins ||= []
end

class Origin
attr_accessor :names, :path, :allow_methods, :allow_headers, :max_age, :allow_credentials

def initialize(names, path, opts={})
self.names = [names].flatten.collect{|n| "http://#{n}" unless n.match(/^https?:\/\//)}
self.path = path

self.allow_methods = ensure_enum(opts[:allow_methods]) || [:get]
self.allow_credentials = opts[:allow_credentials] || true
self.max_age = opts[:max_age] || 1728000
self.allow_headers = ensure_enum(opts[:allow_headers]) || nil
protected
def all_resources
@all_resources ||= []
end

def match?(source)
names.include?(source)
def process_preflight(env)
resource = find_resource(env['HTTP_ORIGIN'], env['PATH_INFO'])
resource.to_preflight_headers(env) if resource
end

def to_headers(env)
{ 'Access-Control-Allow-Origin' => env['HTTP_ORIGIN'],
'Access-Control-Allow-Methods' => allow_methods_header,
'Access-Control-Allow-Credentials' => allow_credentials.to_s,
'Access-Control-Max-Age' => max_age.to_s }
def process_cors(env)
resource = find_resource(env['HTTP_ORIGIN'], env['PATH_INFO'])
resource.to_headers(env) if resource
end

def to_preflight_headers(env)
h = to_headers(env)
h.merge!('Access-Control-Allow-Headers' => allow_headers.join(', ')) if allow_headers
h
def find_resource(origin, path)
allowed = all_resources.detect {|r| r.allow_origin?(origin)}
allowed ? allowed.find_resource(path) : nil
end

protected
def allow_methods_header
allow_methods.collect{|m| m.to_s.upcase}.join(', ')
class Resources
def initialize
@origins = []
@resources = []
end

def ensure_enum(v)
return nil if v.nil?
[v] unless v.respond_to?(:join)
def origins(*args)
@origins = args.flatten.collect{|n| "http://#{n}" unless n.match(/^https?:\/\//)}
end
end

protected
def process_preflight(env)
origin = find_origin(env['HTTP_ORIGIN'])
origin.to_preflight_headers(env) if origin
end
def resource(path, opts={})
@resources << Resource.new(path, opts)
end

def process_cors(env)
origin = find_origin(env['HTTP_ORIGIN'])
origin.to_headers(env) if origin
def allow_origin?(source)
@origins.include?(source)
end

def find_resource(path)
@resources.detect{|r| r.match?(path)}
end
end

def find_origin(source_origin)
origins.detect {|origin| origin.match?(source_origin)}
class Resource
attr_accessor :path, :methods, :headers, :max_age, :credentials, :pattern

def initialize(path, opts = {})
self.path = path
self.methods = ensure_enum(opts[:methods]) || [:get]
self.credentials = opts[:credentials] || true
self.headers = ensure_enum(opts[:headers]) || nil
self.max_age = opts[:max_age] || 1728000
self.pattern = compile(path)
end

def match?(path)
pattern =~ path
end

def to_headers(env)
{ 'Access-Control-Allow-Origin' => env['HTTP_ORIGIN'],
'Access-Control-Allow-Methods' => methods.collect{|m| m.to_s.upcase}.join(', '),
'Access-Control-Allow-Credentials' => credentials.to_s,
'Access-Control-Max-Age' => max_age.to_s }
end

def to_preflight_headers(env)
h = to_headers(env)
h.merge!('Access-Control-Allow-Headers' => headers.join(', ')) if headers
h
end

protected
def ensure_enum(v)
return nil if v.nil?
[v] unless v.respond_to?(:join)
end

def compile(path)
if path.respond_to? :to_str
special_chars = %w{. + ( )}
pattern =
path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
case match
when "*"
"(.*?)"
when *special_chars
Regexp.escape(match)
else
"([^/?&#]+)"
end
end
/^#{pattern}$/
elsif path.respond_to? :match
path
else
raise TypeError, path
end
end
end

end
end

0 comments on commit e6d33ff

Please sign in to comment.