rack-cors 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
|