rack-cors 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack-cors might be problematic. Click here for more details.
- data/README.rdoc +7 -3
- data/VERSION +1 -1
- data/lib/rack/cors.rb +91 -21
- data/rack-cors.gemspec +8 -3
- data/test/cors_test.rb +80 -0
- data/test/test.ru +24 -0
- metadata +9 -7
data/README.rdoc
CHANGED
@@ -19,14 +19,18 @@ Install the gem:
|
|
19
19
|
|
20
20
|
You configure Rack::Cors by passing a block to the <tt>use</tt> command:
|
21
21
|
|
22
|
-
use Rack::Cors do |
|
23
|
-
|
22
|
+
use Rack::Cors do |cfg|
|
23
|
+
cfg.allow do |allow|
|
24
24
|
allow.origins 'localhost:3000', '127.0.0.1:3000'
|
25
25
|
|
26
26
|
allow.resource '/file/list_all/', :headers => 'x-domain-token'
|
27
|
-
|
28
27
|
allow.resource '/file/at/*',
|
29
28
|
:methods => [:get, :post, :put, :delete],
|
30
29
|
:headers => 'x-domain-token'
|
31
30
|
end
|
31
|
+
|
32
|
+
cfg.allow do |allow|
|
33
|
+
allow.origins '*'
|
34
|
+
allow.resource '/public/*', :headers => :any, :methods => :get
|
35
|
+
end
|
32
36
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/rack/cors.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class Cors
|
3
|
-
|
4
|
-
def initialize(app)
|
5
|
+
def initialize(app, opts={})
|
5
6
|
@app = app
|
7
|
+
@logger = opts[:logger]
|
6
8
|
yield self if block_given?
|
7
9
|
end
|
8
10
|
|
@@ -14,11 +16,24 @@ module Rack
|
|
14
16
|
def call(env)
|
15
17
|
cors_headers = nil
|
16
18
|
if env['HTTP_ORIGIN']
|
19
|
+
debug(env) do
|
20
|
+
[ 'Incoming Headers:',
|
21
|
+
" Origin: #{env['HTTP_ORIGIN']}",
|
22
|
+
" Access-Control-Request-Method: #{env['HTTP_ACCESS_CONTROL_REQUEST_METHOD']}",
|
23
|
+
" Access-Control-Request-Headers: #{env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}"
|
24
|
+
].join("\n")
|
25
|
+
end
|
17
26
|
if env['REQUEST_METHOD'] == 'OPTIONS'
|
18
|
-
headers = process_preflight(env)
|
19
|
-
|
27
|
+
if headers = process_preflight(env)
|
28
|
+
debug(env) do
|
29
|
+
"Preflight Headers:\n" +
|
30
|
+
headers.collect{|kv| " #{kv.join(': ')}"}.join("\n")
|
31
|
+
end
|
32
|
+
return [200, headers, []]
|
33
|
+
end
|
34
|
+
else
|
35
|
+
cors_headers = process_cors(env)
|
20
36
|
end
|
21
|
-
cors_headers = process_cors(env)
|
22
37
|
end
|
23
38
|
status, headers, body = @app.call env
|
24
39
|
headers = headers.merge(cors_headers) if cors_headers
|
@@ -26,13 +41,20 @@ module Rack
|
|
26
41
|
end
|
27
42
|
|
28
43
|
protected
|
44
|
+
def debug(env, message = nil, &block)
|
45
|
+
logger = @logger || env['rack.logger'] || begin
|
46
|
+
@logger = ::Logger.new(STDOUT).tap {|logger| logger.level = ::Logger::Severity::DEBUG}
|
47
|
+
end
|
48
|
+
logger.debug(message, &block)
|
49
|
+
end
|
50
|
+
|
29
51
|
def all_resources
|
30
52
|
@all_resources ||= []
|
31
53
|
end
|
32
54
|
|
33
55
|
def process_preflight(env)
|
34
56
|
resource = find_resource(env['HTTP_ORIGIN'], env['PATH_INFO'])
|
35
|
-
resource.
|
57
|
+
resource && resource.process_preflight(env)
|
36
58
|
end
|
37
59
|
|
38
60
|
def process_cors(env)
|
@@ -47,20 +69,34 @@ module Rack
|
|
47
69
|
|
48
70
|
class Resources
|
49
71
|
def initialize
|
50
|
-
@origins
|
72
|
+
@origins = []
|
51
73
|
@resources = []
|
74
|
+
@public_resources = false
|
52
75
|
end
|
53
76
|
|
54
77
|
def origins(*args)
|
55
|
-
@origins = args.flatten.collect
|
78
|
+
@origins = args.flatten.collect do |n|
|
79
|
+
case n
|
80
|
+
when /^https?:\/\// then n
|
81
|
+
when '*'
|
82
|
+
@public_resources = true
|
83
|
+
n
|
84
|
+
else
|
85
|
+
"http://#{n}"
|
86
|
+
end
|
87
|
+
end
|
56
88
|
end
|
57
89
|
|
58
90
|
def resource(path, opts={})
|
59
|
-
@resources << Resource.new(path, opts)
|
91
|
+
@resources << Resource.new(public_resources?, path, opts)
|
92
|
+
end
|
93
|
+
|
94
|
+
def public_resources?
|
95
|
+
@public_resources
|
60
96
|
end
|
61
97
|
|
62
98
|
def allow_origin?(source)
|
63
|
-
@origins.include?(source)
|
99
|
+
public_resources? || @origins.include?(source)
|
64
100
|
end
|
65
101
|
|
66
102
|
def find_resource(path)
|
@@ -71,36 +107,70 @@ module Rack
|
|
71
107
|
class Resource
|
72
108
|
attr_accessor :path, :methods, :headers, :max_age, :credentials, :pattern
|
73
109
|
|
74
|
-
def initialize(path, opts
|
110
|
+
def initialize(public_resource, path, opts={})
|
75
111
|
self.path = path
|
76
112
|
self.methods = ensure_enum(opts[:methods]) || [:get]
|
77
113
|
self.credentials = opts[:credentials] || true
|
78
|
-
self.headers = ensure_enum(opts[:headers]) || nil
|
79
114
|
self.max_age = opts[:max_age] || 1728000
|
80
115
|
self.pattern = compile(path)
|
116
|
+
@public_resource = public_resource
|
117
|
+
|
118
|
+
self.headers = case opts[:headers]
|
119
|
+
when :any then :any
|
120
|
+
when nil then nil
|
121
|
+
else
|
122
|
+
[opts[:headers]].flatten.collect{|h| h.downcase}
|
123
|
+
end
|
81
124
|
end
|
82
125
|
|
83
126
|
def match?(path)
|
84
127
|
pattern =~ path
|
85
128
|
end
|
86
129
|
|
87
|
-
def
|
88
|
-
|
89
|
-
|
90
|
-
'Access-Control-Allow-Credentials' => credentials.to_s,
|
91
|
-
'Access-Control-Max-Age' => max_age.to_s }
|
130
|
+
def process_preflight(env)
|
131
|
+
return nil if invalid_method_request?(env) || invalid_headers_request?(env)
|
132
|
+
to_preflight_headers(env)
|
92
133
|
end
|
93
134
|
|
94
|
-
def
|
95
|
-
h =
|
96
|
-
|
135
|
+
def to_headers(env)
|
136
|
+
h = { 'Access-Control-Allow-Origin' => public_resource? ? '*' : env['HTTP_ORIGIN'],
|
137
|
+
'Access-Control-Allow-Methods' => methods.collect{|m| m.to_s.upcase}.join(', '),
|
138
|
+
'Access-Control-Max-Age' => max_age.to_s }
|
139
|
+
h['Access-Control-Allow-Credentials'] = 'true' if credentials
|
97
140
|
h
|
98
141
|
end
|
99
142
|
|
100
143
|
protected
|
144
|
+
def public_resource?
|
145
|
+
@public_resource
|
146
|
+
end
|
147
|
+
|
148
|
+
def to_preflight_headers(env)
|
149
|
+
h = to_headers(env)
|
150
|
+
if env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']
|
151
|
+
h.merge!('Access-Control-Allow-Headers' => env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])
|
152
|
+
end
|
153
|
+
h
|
154
|
+
end
|
155
|
+
|
156
|
+
def invalid_method_request?(env)
|
157
|
+
request_method = env['HTTP_ACCESS_CONTROL_REQUEST_METHOD']
|
158
|
+
request_method.nil? || !methods.include?(request_method.downcase.to_sym)
|
159
|
+
end
|
160
|
+
|
161
|
+
def invalid_headers_request?(env)
|
162
|
+
request_headers = env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']
|
163
|
+
request_headers && !allow_headers?(request_headers)
|
164
|
+
end
|
165
|
+
|
166
|
+
def allow_headers?(request_headers)
|
167
|
+
return false if headers.nil?
|
168
|
+
headers == :any || !request_headers.detect{|h| !headers.include?(h.downcase)}
|
169
|
+
end
|
170
|
+
|
101
171
|
def ensure_enum(v)
|
102
172
|
return nil if v.nil?
|
103
|
-
[v]
|
173
|
+
[v].flatten
|
104
174
|
end
|
105
175
|
|
106
176
|
def compile(path)
|
data/rack-cors.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{rack-cors}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Calvin Yu"]
|
12
|
-
s.date = %q{2010-06-
|
12
|
+
s.date = %q{2010-06-08}
|
13
13
|
s.email = %q{csyu77@gmail.com}
|
14
14
|
s.extra_rdoc_files = [
|
15
15
|
"README.rdoc"
|
@@ -20,13 +20,18 @@ Gem::Specification.new do |s|
|
|
20
20
|
"Rakefile",
|
21
21
|
"VERSION",
|
22
22
|
"lib/rack/cors.rb",
|
23
|
-
"rack-cors.gemspec"
|
23
|
+
"rack-cors.gemspec",
|
24
|
+
"test/cors_test.rb",
|
25
|
+
"test/test.ru"
|
24
26
|
]
|
25
27
|
s.homepage = %q{http://github.com/cyu/rack-cors}
|
26
28
|
s.rdoc_options = ["--charset=UTF-8"]
|
27
29
|
s.require_paths = ["lib"]
|
28
30
|
s.rubygems_version = %q{1.3.7}
|
29
31
|
s.summary = %q{Middleware for enabling Cross-Origin Resource Sharing in Rack apps}
|
32
|
+
s.test_files = [
|
33
|
+
"test/cors_test.rb"
|
34
|
+
]
|
30
35
|
|
31
36
|
if s.respond_to? :specification_version then
|
32
37
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
data/test/cors_test.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rack/test'
|
3
|
+
require 'shoulda'
|
4
|
+
|
5
|
+
Rack::Test::Session.class_eval do
|
6
|
+
def options(uri, params = {}, env = {}, &block)
|
7
|
+
env = env_for(uri, env.merge(:method => "OPTIONS", :params => params))
|
8
|
+
process_request(uri, env, &block)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
Rack::Test::Methods.class_eval do
|
13
|
+
def_delegator :current_session, :options
|
14
|
+
end
|
15
|
+
|
16
|
+
class CorsTest < Test::Unit::TestCase
|
17
|
+
include Rack::Test::Methods
|
18
|
+
|
19
|
+
def app
|
20
|
+
eval "Rack::Builder.new {( " + File.read(File.dirname(__FILE__) + '/test.ru') + "\n )}"
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'preflight requests' do
|
24
|
+
should 'fail if origin is invalid' do
|
25
|
+
preflight_request('http://allyourdataarebelongtous.com', '/')
|
26
|
+
assert_preflight_failure
|
27
|
+
end
|
28
|
+
|
29
|
+
should 'fail if Access-Control-Request-Method does not exist' do
|
30
|
+
preflight_request('http://localhost:3000', '/', :method => nil)
|
31
|
+
assert_preflight_failure
|
32
|
+
end
|
33
|
+
|
34
|
+
should 'fail if Access-Control-Request-Method is not allowed' do
|
35
|
+
preflight_request('http://localhost:3000', '/get-only', :method => :post)
|
36
|
+
assert_preflight_failure
|
37
|
+
end
|
38
|
+
|
39
|
+
should 'fail if header is not allowed' do
|
40
|
+
preflight_request('http://localhost:3000', '/single_header', :headers => 'Fooey')
|
41
|
+
assert_preflight_failure
|
42
|
+
end
|
43
|
+
|
44
|
+
should 'allow any header if headers = :any' do
|
45
|
+
preflight_request('http://localhost:3000', '/', :headers => 'Fooey')
|
46
|
+
assert_preflight_success
|
47
|
+
end
|
48
|
+
|
49
|
+
should 'allow header case insensitive match' do
|
50
|
+
preflight_request('http://localhost:3000', '/single_header', :headers => 'X-Domain-Token')
|
51
|
+
assert_preflight_success
|
52
|
+
end
|
53
|
+
|
54
|
+
should '* origin should allow any origin' do
|
55
|
+
preflight_request('http://locohost:3000', '/public')
|
56
|
+
assert_preflight_success
|
57
|
+
assert_equal '*', last_response.headers['Access-Control-Allow-Origin']
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
def preflight_request(origin, path, opts = {})
|
63
|
+
header 'Origin', origin
|
64
|
+
unless opts.key?(:method) && opts[:method].nil?
|
65
|
+
header 'Access-Control-Request-Method', opts[:method] ? opts[:method].to_s.upcase : 'GET'
|
66
|
+
end
|
67
|
+
if opts[:headers]
|
68
|
+
header 'Access-Control-Request-Headers', [opts[:headers]].flatten.join(', ')
|
69
|
+
end
|
70
|
+
options path
|
71
|
+
end
|
72
|
+
|
73
|
+
def assert_preflight_success
|
74
|
+
assert_not_nil last_response.headers['Access-Control-Allow-Origin']
|
75
|
+
end
|
76
|
+
|
77
|
+
def assert_preflight_failure
|
78
|
+
assert_nil last_response.headers['Access-Control-Allow-Origin']
|
79
|
+
end
|
80
|
+
end
|
data/test/test.ru
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rack/cors'
|
2
|
+
|
3
|
+
use Rack::Cors do |cfg|
|
4
|
+
cfg.allow do |allow|
|
5
|
+
allow.origins 'localhost:3000', '127.0.0.1:3000'
|
6
|
+
|
7
|
+
allow.resource '/get-only', :methods => :get
|
8
|
+
allow.resource '/', :headers => :any
|
9
|
+
allow.resource '/single_header', :headers => 'x-domain-token'
|
10
|
+
# allow.resource '/file/at/*',
|
11
|
+
# :methods => [:get, :post, :put, :delete],
|
12
|
+
# :headers => :any,
|
13
|
+
# :max_age => 0
|
14
|
+
end
|
15
|
+
|
16
|
+
cfg.allow do |allow|
|
17
|
+
allow.origins '*'
|
18
|
+
allow.resource '/public'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
map '/' do
|
23
|
+
run Proc.new { |env| [200, {'Content-Type' => 'text/html'}, ['success']] }
|
24
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-cors
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Calvin Yu
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-06-
|
18
|
+
date: 2010-06-08 00:00:00 -04:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -34,6 +34,8 @@ files:
|
|
34
34
|
- VERSION
|
35
35
|
- lib/rack/cors.rb
|
36
36
|
- rack-cors.gemspec
|
37
|
+
- test/cors_test.rb
|
38
|
+
- test/test.ru
|
37
39
|
has_rdoc: true
|
38
40
|
homepage: http://github.com/cyu/rack-cors
|
39
41
|
licenses: []
|
@@ -68,5 +70,5 @@ rubygems_version: 1.3.7
|
|
68
70
|
signing_key:
|
69
71
|
specification_version: 3
|
70
72
|
summary: Middleware for enabling Cross-Origin Resource Sharing in Rack apps
|
71
|
-
test_files:
|
72
|
-
|
73
|
+
test_files:
|
74
|
+
- test/cors_test.rb
|