Skip to content

Commit

Permalink
Store CORS result in environment
Browse files Browse the repository at this point in the history
Store the CORS result in env['X_Rack_CORS'] so it can be accessible
by other parts of the Rack stack
  • Loading branch information
cyu committed Oct 20, 2014
1 parent e2a08d4 commit 155eeb7
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 35 deletions.
104 changes: 87 additions & 17 deletions lib/rack/cors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

module Rack
class Cors
HEADER_KEY = 'X-Rack-CORS'
ENV_KEY = 'X_RACK_CORS'.freeze

ORIGIN_HEADER_KEY = 'HTTP_ORIGIN'.freeze
PATH_INFO_HEADER_KEY = 'PATH_INFO'.freeze

def initialize(app, opts={}, &block)
@app = app
Expand Down Expand Up @@ -40,13 +43,13 @@ def allow(&block)
end

def call(env)
env['HTTP_ORIGIN'] ||= env['HTTP_X_ORIGIN']
env[ORIGIN_HEADER_KEY] ||= env['HTTP_X_ORIGIN']

add_headers = nil
if env['HTTP_ORIGIN']
if env[ORIGIN_HEADER_KEY]
debug(env) do
[ 'Incoming Headers:',
" Origin: #{env['HTTP_ORIGIN']}",
" Origin: #{env[ORIGIN_HEADER_KEY]}",
" Access-Control-Request-Method: #{env['HTTP_ACCESS_CONTROL_REQUEST_METHOD']}",
" Access-Control-Request-Headers: #{env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}"
].join("\n")
Expand All @@ -62,8 +65,8 @@ def call(env)
else
add_headers = process_cors(env)
end
elsif debug?
add_headers = { HEADER_KEY => "miss; no-origin-header" }
else
Result.miss(env, Result::MISS_NO_ORIGIN)
end

status, headers, body = @app.call env
Expand All @@ -78,6 +81,10 @@ def call(env)
end
end

if debug? && result = env[ENV_KEY]
result.append_header(headers)
end

[status, headers, body]
end

Expand Down Expand Up @@ -108,30 +115,35 @@ def all_resources
end

def process_preflight(env)
resource, error = find_resource(env['HTTP_ORIGIN'], env['PATH_INFO'],env)
resource, error = find_resource(env)
if resource
Result.preflight_hit(env)
preflight = resource.process_preflight(env)
preflight.merge!(HEADER_KEY => 'preflight-hit') if debug?
preflight

elsif debug?
{ HEADER_KEY => ['preflight-miss', error].compact.join('; ') }
else
Result.preflight_miss(env, error)
nil
end
end

def process_cors(env)
resource, error = find_resource(env['HTTP_ORIGIN'], env['PATH_INFO'],env)
resource, error = find_resource(env)
if resource
Result.hit(env)
cors = resource.to_headers(env)
cors.merge!(HEADER_KEY => 'hit') if debug?
cors

elsif debug?
{ HEADER_KEY => ['miss', error].compact.join('; ') }
else
Result.miss(env, error)
nil
end
end

def find_resource(origin, path, env)
def find_resource(env)
path = env[PATH_INFO_HEADER_KEY]
origin = env[ORIGIN_HEADER_KEY]

origin_matched = false
all_resources.each do |r|
if r.allow_origin?(origin, env)
Expand All @@ -142,7 +154,65 @@ def find_resource(origin, path, env)
end
end

[nil, origin_matched ? 'no-path-match' : 'no-origin-match']
[nil, origin_matched ? Result::MISS_NO_PATH : Result::MISS_NO_ORIGIN]
end

class Result
HEADER_KEY = 'X-Rack-CORS'.freeze

MISS_NO_ORIGIN = 'no-origin'.freeze
MISS_NO_PATH = 'no-path'.freeze

attr_accessor :preflight, :hit, :miss_reason

def hit?
!!hit
end

def preflight?
!!preflight
end

def self.hit(env)
r = Result.new
r.preflight = false
r.hit = true
env[ENV_KEY] = r
end

def self.miss(env, reason)
r = Result.new
r.preflight = false
r.hit = false
r.miss_reason = reason
env[ENV_KEY] = r
end

def self.preflight_hit(env)
r = Result.new
r.preflight = true
r.hit = true
env[ENV_KEY] = r
end

def self.preflight_miss(env, reason)
r = Result.new
r.preflight = true
r.hit = false
r.miss_reason = reason
env[ENV_KEY] = r
end

def append_header(headers)
headers[HEADER_KEY] = if hit?
preflight? ? 'preflight-hit' : 'hit'
else
[
(preflight? ? 'preflight-miss' : 'preflight-hit'),
miss_reason
].join('; ')
end
end
end

class Resources
Expand Down Expand Up @@ -225,7 +295,7 @@ def process_preflight(env)
def to_headers(env)
x_origin = env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']
h = {
'Access-Control-Allow-Origin' => origin_for_response_header(env['HTTP_ORIGIN']),
'Access-Control-Allow-Origin' => origin_for_response_header(env[ORIGIN_HEADER_KEY]),
'Access-Control-Allow-Methods' => methods.collect{|m| m.to_s.upcase}.join(', '),
'Access-Control-Expose-Headers' => expose.nil? ? '' : expose.join(', '),
'Access-Control-Max-Age' => max_age.to_s }
Expand Down
41 changes: 31 additions & 10 deletions test/unit/cors_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,35 @@ def options(uri, params = {}, env = {}, &block)
describe Rack::Cors do
include Rack::Test::Methods

attr_accessor :cors_result

def load_app(name)
eval "Rack::Builder.new {( " + File.read(File.dirname(__FILE__) + "/#{name}.ru") + "\n )}"
test = self
Rack::Builder.new do
eval File.read(File.dirname(__FILE__) + "/#{name}.ru")
map('/') do
run proc { |env|
test.cors_result = env[Rack::Cors::ENV_KEY]
[200, {'Content-Type' => 'text/html'}, ['success']]
}
end
end
end

let(:app) do
load_app('test')
end
let(:app) { load_app('test') }

it 'should support simple cors request' do
it 'should support simple CORS request' do
cors_request
cors_result.must_be :hit
end

it "should not return CORS headers if Origin header isn't present" do
get '/'
should_render_cors_failure
cors_result.wont_be :hit
end

it 'should support OPTIONS cors request' do
it 'should support OPTIONS CORS request' do
cors_request '/options', :method => :options
end

Expand Down Expand Up @@ -81,6 +97,11 @@ def load_app(name)
last_response.headers['Vary'].must_be_nil
end

it "should not return CORS headers on OPTIONS request if Access-Control-Allow-Origin is not present" do
options '/get-only'
last_response.headers['Access-Control-Allow-Origin'].must_be_nil
end

describe 'logging' do
it 'should not log debug messages if debug option is false' do
app = mock
Expand Down Expand Up @@ -150,6 +171,8 @@ def load_app(name)
it 'should fail if origin is invalid' do
preflight_request('http://allyourdataarebelongtous.com', '/')
should_render_cors_failure
cors_result.wont_be :hit
cors_result.must_be :preflight
end

it 'should fail if Access-Control-Request-Method is not allowed' do
Expand Down Expand Up @@ -214,9 +237,7 @@ def load_app(name)
end

describe "with non HTTP config" do
let(:app) do
load_app("non_http")
end
let(:app) { load_app("non_http") }

it 'should support non http/https origins' do
cors_request '/public', origin: 'content://com.company.app'
Expand All @@ -231,7 +252,7 @@ def cors_request(*args)
opts.merge! args.last if args.last.is_a?(Hash)

header 'Origin', opts[:origin]
current_session.__send__ opts[:method], path
current_session.__send__ opts[:method], path, {}, test: self
should_render_cors_success
end

Expand Down
4 changes: 0 additions & 4 deletions test/unit/non_http.ru
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,3 @@ use Rack::Cors do
resource '/public'
end
end

map '/' do
run Proc.new { |env| [200, {'Content-Type' => 'text/html'}, ['success']] }
end
4 changes: 0 additions & 4 deletions test/unit/test.ru
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,3 @@ use Rack::Cors do
resource '/multi-allow-config', :max_age => 300, :credentials => false
end
end

map '/' do
run Proc.new { |env| [200, {'Content-Type' => 'text/html'}, ['success']] }
end

0 comments on commit 155eeb7

Please sign in to comment.