rack-cors 0.2.9 → 1.0.5
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.
- checksums.yaml +5 -13
- data/.travis.yml +8 -0
- data/CHANGELOG.md +73 -0
- data/Gemfile +2 -0
- data/README.md +139 -0
- data/lib/rack/cors.rb +285 -74
- data/lib/rack/cors/version.rb +1 -1
- data/rack-cors.gemspec +8 -7
- data/test/cors/test.cors.coffee +26 -2
- data/test/cors/test.cors.js +45 -4
- data/test/unit/cors_test.rb +422 -95
- data/test/unit/dsl_test.rb +28 -17
- data/test/unit/insecure.ru +8 -0
- data/test/unit/non_http.ru +8 -0
- data/test/unit/test.ru +20 -5
- metadata +56 -32
- data/README.rdoc +0 -66
data/lib/rack/cors/version.rb
CHANGED
data/rack-cors.gemspec
CHANGED
@@ -8,9 +8,9 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Rack::Cors::VERSION
|
9
9
|
spec.authors = ["Calvin Yu"]
|
10
10
|
spec.email = ["me@sourcebender.com"]
|
11
|
-
spec.description = %q{Middleware that will make Rack-based apps CORS compatible.
|
11
|
+
spec.description = %q{Middleware that will make Rack-based apps CORS compatible. Fork the project here: https://github.com/cyu/rack-cors}
|
12
12
|
spec.summary = %q{Middleware for enabling Cross-Origin Resource Sharing in Rack apps}
|
13
|
-
spec.homepage = "
|
13
|
+
spec.homepage = "https://github.com/cyu/rack-cors"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/).reject { |f| f == '.gitignore' or f =~ /^examples/ }
|
@@ -18,9 +18,10 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.
|
22
|
-
spec.add_development_dependency "
|
23
|
-
spec.add_development_dependency "
|
24
|
-
spec.add_development_dependency "
|
25
|
-
spec.add_development_dependency "
|
21
|
+
spec.add_dependency "rack", ">= 1.6.0"
|
22
|
+
spec.add_development_dependency "bundler", ">= 1.16.0", '< 3'
|
23
|
+
spec.add_development_dependency "rake", "~> 12.3.0"
|
24
|
+
spec.add_development_dependency "minitest", "~> 5.11.0"
|
25
|
+
spec.add_development_dependency "mocha", "~> 1.6.0"
|
26
|
+
spec.add_development_dependency "rack-test", "~> 1.1.0"
|
26
27
|
end
|
data/test/cors/test.cors.coffee
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
CORS_SERVER = '
|
1
|
+
CORS_SERVER = '127.0.0.1.xip.io:9292'
|
2
2
|
|
3
3
|
describe 'CORS', ->
|
4
4
|
|
@@ -7,6 +7,31 @@ describe 'CORS', ->
|
|
7
7
|
expect(data).to.eql('Hello world')
|
8
8
|
done()
|
9
9
|
|
10
|
+
it 'should allow PUT access to dynamic resource', (done) ->
|
11
|
+
$.ajax("http://#{CORS_SERVER}/", type: 'PUT').done (data, textStatus, jqXHR) ->
|
12
|
+
expect(data).to.eql('Hello world')
|
13
|
+
done()
|
14
|
+
|
15
|
+
it 'should allow PATCH access to dynamic resource', (done) ->
|
16
|
+
$.ajax("http://#{CORS_SERVER}/", type: 'PATCH').done (data, textStatus, jqXHR) ->
|
17
|
+
expect(data).to.eql('Hello world')
|
18
|
+
done()
|
19
|
+
|
20
|
+
it 'should allow HEAD access to dynamic resource', (done) ->
|
21
|
+
$.ajax("http://#{CORS_SERVER}/", type: 'HEAD').done (data, textStatus, jqXHR) ->
|
22
|
+
expect(jqXHR.status).to.eql(200)
|
23
|
+
done()
|
24
|
+
|
25
|
+
it 'should allow DELETE access to dynamic resource', (done) ->
|
26
|
+
$.ajax("http://#{CORS_SERVER}/", type: 'DELETE').done (data, textStatus, jqXHR) ->
|
27
|
+
expect(data).to.eql('Hello world')
|
28
|
+
done()
|
29
|
+
|
30
|
+
it 'should allow OPTIONS access to dynamic resource', (done) ->
|
31
|
+
$.ajax("http://#{CORS_SERVER}/", type: 'OPTIONS').done (data, textStatus, jqXHR) ->
|
32
|
+
expect(jqXHR.status).to.eql(200)
|
33
|
+
done()
|
34
|
+
|
10
35
|
it 'should allow access to static resource', (done) ->
|
11
36
|
$.get "http://#{CORS_SERVER}/static.txt", (data, status, xhr) ->
|
12
37
|
expect($.trim(data)).to.eql("hello world")
|
@@ -20,4 +45,3 @@ describe 'CORS', ->
|
|
20
45
|
success:(data, status, xhr) ->
|
21
46
|
expect($.trim(data)).to.eql("OK!")
|
22
47
|
done()
|
23
|
-
|
data/test/cors/test.cors.js
CHANGED
@@ -1,17 +1,58 @@
|
|
1
|
+
// Generated by CoffeeScript 2.3.1
|
1
2
|
(function() {
|
2
3
|
var CORS_SERVER;
|
3
4
|
|
4
|
-
CORS_SERVER = '
|
5
|
+
CORS_SERVER = '127.0.0.1.xip.io:9292';
|
5
6
|
|
6
7
|
describe('CORS', function() {
|
7
8
|
it('should allow access to dynamic resource', function(done) {
|
8
|
-
return $.get(
|
9
|
+
return $.get(`http://${CORS_SERVER}/`, function(data, status, xhr) {
|
9
10
|
expect(data).to.eql('Hello world');
|
10
11
|
return done();
|
11
12
|
});
|
12
13
|
});
|
14
|
+
it('should allow PUT access to dynamic resource', function(done) {
|
15
|
+
return $.ajax(`http://${CORS_SERVER}/`, {
|
16
|
+
type: 'PUT'
|
17
|
+
}).done(function(data, textStatus, jqXHR) {
|
18
|
+
expect(data).to.eql('Hello world');
|
19
|
+
return done();
|
20
|
+
});
|
21
|
+
});
|
22
|
+
it('should allow PATCH access to dynamic resource', function(done) {
|
23
|
+
return $.ajax(`http://${CORS_SERVER}/`, {
|
24
|
+
type: 'PATCH'
|
25
|
+
}).done(function(data, textStatus, jqXHR) {
|
26
|
+
expect(data).to.eql('Hello world');
|
27
|
+
return done();
|
28
|
+
});
|
29
|
+
});
|
30
|
+
it('should allow HEAD access to dynamic resource', function(done) {
|
31
|
+
return $.ajax(`http://${CORS_SERVER}/`, {
|
32
|
+
type: 'HEAD'
|
33
|
+
}).done(function(data, textStatus, jqXHR) {
|
34
|
+
expect(jqXHR.status).to.eql(200);
|
35
|
+
return done();
|
36
|
+
});
|
37
|
+
});
|
38
|
+
it('should allow DELETE access to dynamic resource', function(done) {
|
39
|
+
return $.ajax(`http://${CORS_SERVER}/`, {
|
40
|
+
type: 'DELETE'
|
41
|
+
}).done(function(data, textStatus, jqXHR) {
|
42
|
+
expect(data).to.eql('Hello world');
|
43
|
+
return done();
|
44
|
+
});
|
45
|
+
});
|
46
|
+
it('should allow OPTIONS access to dynamic resource', function(done) {
|
47
|
+
return $.ajax(`http://${CORS_SERVER}/`, {
|
48
|
+
type: 'OPTIONS'
|
49
|
+
}).done(function(data, textStatus, jqXHR) {
|
50
|
+
expect(jqXHR.status).to.eql(200);
|
51
|
+
return done();
|
52
|
+
});
|
53
|
+
});
|
13
54
|
it('should allow access to static resource', function(done) {
|
14
|
-
return $.get(
|
55
|
+
return $.get(`http://${CORS_SERVER}/static.txt`, function(data, status, xhr) {
|
15
56
|
expect($.trim(data)).to.eql("hello world");
|
16
57
|
return done();
|
17
58
|
});
|
@@ -19,7 +60,7 @@
|
|
19
60
|
return it('should allow post resource', function(done) {
|
20
61
|
return $.ajax({
|
21
62
|
type: 'POST',
|
22
|
-
url:
|
63
|
+
url: `http://${CORS_SERVER}/cors`,
|
23
64
|
beforeSend: function(xhr) {
|
24
65
|
return xhr.setRequestHeader('X-Requested-With', 'XMLHTTPRequest');
|
25
66
|
},
|
data/test/unit/cors_test.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
|
-
require '
|
2
|
-
require 'test/unit'
|
1
|
+
require 'minitest/autorun'
|
3
2
|
require 'rack/test'
|
4
|
-
require 'shoulda'
|
5
3
|
require 'mocha/setup'
|
6
4
|
require 'rack/cors'
|
5
|
+
require 'ostruct'
|
7
6
|
|
8
7
|
Rack::Test::Session.class_eval do
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
unless defined? :options
|
9
|
+
def options(uri, params = {}, env = {}, &block)
|
10
|
+
env = env_for(uri, env.merge(:method => "OPTIONS", :params => params))
|
11
|
+
process_request(uri, env, &block)
|
12
|
+
end
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
@@ -16,149 +17,479 @@ Rack::Test::Methods.class_eval do
|
|
16
17
|
def_delegator :current_session, :options
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
|
+
module MiniTest::Assertions
|
21
|
+
def assert_cors_success(response)
|
22
|
+
assert !response.headers['Access-Control-Allow-Origin'].nil?, "Expected a successful CORS response"
|
23
|
+
end
|
24
|
+
|
25
|
+
def assert_not_cors_success(response)
|
26
|
+
assert response.headers['Access-Control-Allow-Origin'].nil?, "Expected a failed CORS response"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class CaptureResult
|
31
|
+
def initialize(app, options = {})
|
32
|
+
@app = app
|
33
|
+
@result_holder = options[:holder]
|
34
|
+
end
|
35
|
+
|
36
|
+
def call(env)
|
37
|
+
response = @app.call(env)
|
38
|
+
@result_holder.cors_result = env[Rack::Cors::RACK_CORS]
|
39
|
+
return response
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class FakeProxy
|
44
|
+
def initialize(app, options = {})
|
45
|
+
@app = app
|
46
|
+
end
|
47
|
+
|
48
|
+
def call(env)
|
49
|
+
status, headers, body = @app.call(env)
|
50
|
+
headers['Vary'] = %w(Origin User-Agent)
|
51
|
+
[status, headers, body]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
Rack::MockResponse.infect_an_assertion :assert_cors_success, :must_render_cors_success, :only_one_argument
|
56
|
+
Rack::MockResponse.infect_an_assertion :assert_not_cors_success, :wont_render_cors_success, :only_one_argument
|
57
|
+
|
58
|
+
describe Rack::Cors do
|
20
59
|
include Rack::Test::Methods
|
21
60
|
|
22
|
-
|
23
|
-
|
61
|
+
attr_accessor :cors_result
|
62
|
+
|
63
|
+
def load_app(name, options = {})
|
64
|
+
test = self
|
65
|
+
Rack::Builder.new do
|
66
|
+
use CaptureResult, :holder => test
|
67
|
+
eval File.read(File.dirname(__FILE__) + "/#{name}.ru")
|
68
|
+
use FakeProxy if options[:proxy]
|
69
|
+
map('/') do
|
70
|
+
run proc { |env|
|
71
|
+
[200, {'Content-Type' => 'text/html'}, ['success']]
|
72
|
+
}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
let(:app) { load_app('test') }
|
78
|
+
|
79
|
+
it 'should support simple CORS request' do
|
80
|
+
successful_cors_request
|
81
|
+
cors_result.must_be :hit
|
24
82
|
end
|
25
83
|
|
26
|
-
should
|
84
|
+
it "should not return CORS headers if Origin header isn't present" do
|
85
|
+
get '/'
|
86
|
+
last_response.wont_render_cors_success
|
87
|
+
cors_result.wont_be :hit
|
88
|
+
end
|
27
89
|
|
28
|
-
should
|
29
|
-
|
90
|
+
it 'should support OPTIONS CORS request' do
|
91
|
+
successful_cors_request '/options', :method => :options
|
30
92
|
end
|
31
93
|
|
32
|
-
should
|
33
|
-
|
94
|
+
it 'should support regex origins configuration' do
|
95
|
+
successful_cors_request :origin => 'http://192.168.0.1:1234'
|
34
96
|
end
|
35
97
|
|
36
|
-
should
|
37
|
-
|
98
|
+
it 'should support subdomain example' do
|
99
|
+
successful_cors_request :origin => 'http://subdomain.example.com'
|
38
100
|
end
|
39
101
|
|
40
|
-
should
|
102
|
+
it 'should support proc origins configuration' do
|
103
|
+
successful_cors_request '/proc-origin', :origin => 'http://10.10.10.10:3000'
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should support lambda origin configuration' do
|
107
|
+
successful_cors_request '/lambda-origin', :origin => 'http://10.10.10.10:3000'
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should support proc origins configuration (inverse)' do
|
111
|
+
cors_request '/proc-origin', :origin => 'http://bad.guy'
|
112
|
+
last_response.wont_render_cors_success
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should not mix up path rules across origins' do
|
116
|
+
header 'Origin', 'http://10.10.10.10:3000'
|
117
|
+
get '/' # / is configured in a separate rule block
|
118
|
+
last_response.wont_render_cors_success
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should support alternative X-Origin header' do
|
41
122
|
header 'X-Origin', 'http://localhost:3000'
|
42
123
|
get '/'
|
43
|
-
|
124
|
+
last_response.must_render_cors_success
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should support expose header configuration' do
|
128
|
+
successful_cors_request '/expose_single_header'
|
129
|
+
last_response.headers['Access-Control-Expose-Headers'].must_equal 'expose-test'
|
44
130
|
end
|
45
131
|
|
46
|
-
should
|
47
|
-
|
48
|
-
|
132
|
+
it 'should support expose multiple header configuration' do
|
133
|
+
successful_cors_request '/expose_multiple_headers'
|
134
|
+
last_response.headers['Access-Control-Expose-Headers'].must_equal 'expose-test-1, expose-test-2'
|
49
135
|
end
|
50
136
|
|
51
|
-
|
52
|
-
|
53
|
-
|
137
|
+
# Explanation: http://www.fastly.com/blog/best-practices-for-using-the-vary-header/
|
138
|
+
it "should add Vary header if resource matches even if Origin header isn't present" do
|
139
|
+
get '/'
|
140
|
+
last_response.wont_render_cors_success
|
141
|
+
last_response.headers['Vary'].must_equal 'Origin'
|
54
142
|
end
|
55
143
|
|
56
|
-
should
|
57
|
-
|
58
|
-
|
59
|
-
assert_equal 'http://192.168.0.3:8080', last_response.headers['Access-Control-Allow-Origin']
|
60
|
-
assert_not_nil last_response.headers['Vary'], 'missing Vary header'
|
144
|
+
it "should add Vary header based on :vary option" do
|
145
|
+
successful_cors_request '/vary_test'
|
146
|
+
last_response.headers['Vary'].must_equal 'Origin, Host'
|
61
147
|
end
|
62
148
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
assert_nil last_response.headers['Vary'], 'no expecting Vary header'
|
149
|
+
it "decode URL and resolve paths before resource matching" do
|
150
|
+
header 'Origin', 'http://localhost:3000'
|
151
|
+
get '/public/a/..%2F..%2Fprivate/stuff'
|
152
|
+
last_response.wont_render_cors_success
|
68
153
|
end
|
69
154
|
|
70
|
-
|
71
|
-
|
72
|
-
assert_cors_success
|
73
|
-
assert_equal 'http://mucho-grande.com', last_response.headers['Access-Control-Allow-Origin']
|
74
|
-
assert_equal 'Origin', last_response.headers['Vary'], 'expecting Vary header'
|
155
|
+
describe 'with array of upstream Vary headers' do
|
156
|
+
let(:app) { load_app('test', { proxy: true }) }
|
75
157
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
158
|
+
it 'should add to them' do
|
159
|
+
successful_cors_request '/vary_test'
|
160
|
+
last_response.headers['Vary'].must_equal 'Origin, User-Agent, Host'
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'should add Vary header if Access-Control-Allow-Origin header was added and if it is specific' do
|
165
|
+
successful_cors_request '/', :origin => "http://192.168.0.3:8080"
|
166
|
+
last_response.headers['Access-Control-Allow-Origin'].must_equal 'http://192.168.0.3:8080'
|
167
|
+
last_response.headers['Vary'].wont_be_nil
|
80
168
|
end
|
81
169
|
|
82
|
-
|
83
|
-
|
84
|
-
|
170
|
+
it 'should add Vary header even if Access-Control-Allow-Origin header was added and it is generic (*)' do
|
171
|
+
successful_cors_request '/public_without_credentials', :origin => "http://192.168.1.3:8080"
|
172
|
+
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
|
173
|
+
last_response.headers['Vary'].must_equal 'Origin'
|
174
|
+
end
|
85
175
|
|
86
|
-
|
87
|
-
|
176
|
+
it 'should support multi allow configurations for the same resource' do
|
177
|
+
successful_cors_request '/multi-allow-config', :origin => "http://mucho-grande.com"
|
178
|
+
last_response.headers['Access-Control-Allow-Origin'].must_equal 'http://mucho-grande.com'
|
179
|
+
last_response.headers['Vary'].must_equal 'Origin'
|
88
180
|
|
89
|
-
|
90
|
-
|
181
|
+
successful_cors_request '/multi-allow-config', :origin => "http://192.168.1.3:8080"
|
182
|
+
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
|
183
|
+
last_response.headers['Vary'].must_equal 'Origin'
|
91
184
|
end
|
92
185
|
|
93
|
-
should
|
94
|
-
|
95
|
-
|
186
|
+
it "should not return CORS headers on OPTIONS request if Access-Control-Allow-Origin is not present" do
|
187
|
+
options '/get-only'
|
188
|
+
last_response.headers['Access-Control-Allow-Origin'].must_be_nil
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should not apply CORS headers if it does not match conditional on resource" do
|
192
|
+
header 'Origin', 'http://192.168.0.1:1234'
|
193
|
+
get '/conditional'
|
194
|
+
last_response.wont_render_cors_success
|
195
|
+
end
|
96
196
|
|
97
|
-
|
98
|
-
|
197
|
+
it "should apply CORS headers if it does match conditional on resource" do
|
198
|
+
header 'X-OK', '1'
|
199
|
+
successful_cors_request '/conditional', :origin => 'http://192.168.0.1:1234'
|
200
|
+
end
|
99
201
|
|
100
|
-
|
101
|
-
|
202
|
+
it "should not allow everything if Origin is configured as blank string" do
|
203
|
+
cors_request '/blank-origin', origin: "http://example.net"
|
204
|
+
last_response.wont_render_cors_success
|
102
205
|
end
|
103
206
|
|
104
|
-
|
105
|
-
|
207
|
+
it "should not allow credentials for public resources" do
|
208
|
+
successful_cors_request '/public'
|
209
|
+
last_response.headers['Access-Control-Allow-Credentials'].must_be_nil
|
210
|
+
end
|
211
|
+
|
212
|
+
describe 'logging' do
|
213
|
+
it 'should not log debug messages if debug option is false' do
|
214
|
+
app = mock
|
215
|
+
app.stubs(:call).returns(200, {}, [''])
|
216
|
+
|
217
|
+
logger = mock
|
218
|
+
logger.expects(:debug).never
|
219
|
+
|
220
|
+
cors = Rack::Cors.new(app, :debug => false, :logger => logger) {}
|
221
|
+
cors.send(:debug, {}, 'testing')
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'should log debug messages if debug option is true' do
|
225
|
+
app = mock
|
226
|
+
app.stubs(:call).returns(200, {}, [''])
|
227
|
+
|
228
|
+
logger = mock
|
229
|
+
logger.expects(:debug)
|
230
|
+
|
231
|
+
cors = Rack::Cors.new(app, :debug => true, :logger => logger) {}
|
232
|
+
cors.send(:debug, {}, 'testing')
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'should use rack.logger if available' do
|
236
|
+
app = mock
|
237
|
+
app.stubs(:call).returns([200, {}, ['']])
|
238
|
+
|
239
|
+
logger = mock
|
240
|
+
logger.expects(:debug).at_least_once
|
241
|
+
|
242
|
+
cors = Rack::Cors.new(app, :debug => true) {}
|
243
|
+
cors.call({'rack.logger' => logger, 'HTTP_ORIGIN' => 'test.com'})
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'should use logger proc' do
|
247
|
+
app = mock
|
248
|
+
app.stubs(:call).returns([200, {}, ['']])
|
249
|
+
|
250
|
+
logger = mock
|
251
|
+
logger.expects(:debug)
|
252
|
+
|
253
|
+
cors = Rack::Cors.new(app, :debug => true, :logger => proc { logger }) {}
|
254
|
+
cors.call({'HTTP_ORIGIN' => 'test.com'})
|
255
|
+
end
|
256
|
+
|
257
|
+
describe 'with Rails setup' do
|
258
|
+
after do
|
259
|
+
::Rails.logger = nil if defined?(::Rails)
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'should use Rails.logger if available' do
|
263
|
+
app = mock
|
264
|
+
app.stubs(:call).returns([200, {}, ['']])
|
265
|
+
|
266
|
+
logger = mock
|
267
|
+
logger.expects(:debug)
|
268
|
+
|
269
|
+
::Rails = OpenStruct.new(:logger => logger)
|
270
|
+
|
271
|
+
cors = Rack::Cors.new(app, :debug => true) {}
|
272
|
+
cors.call({'HTTP_ORIGIN' => 'test.com'})
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
it 'should use Logger if none is set' do
|
277
|
+
app = mock
|
278
|
+
app.stubs(:call).returns([200, {}, ['']])
|
279
|
+
|
280
|
+
logger = mock
|
281
|
+
Logger.expects(:new).returns(logger)
|
282
|
+
logger.expects(:tap).returns(logger)
|
283
|
+
logger.expects(:debug)
|
284
|
+
|
285
|
+
cors = Rack::Cors.new(app, :debug => true) {}
|
286
|
+
cors.call({'HTTP_ORIGIN' => 'test.com'})
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
describe 'preflight requests' do
|
291
|
+
it 'should fail if origin is invalid' do
|
106
292
|
preflight_request('http://allyourdataarebelongtous.com', '/')
|
107
|
-
|
293
|
+
last_response.wont_render_cors_success
|
294
|
+
cors_result.wont_be :hit
|
295
|
+
cors_result.must_be :preflight
|
108
296
|
end
|
109
297
|
|
110
|
-
should
|
298
|
+
it 'should fail if Access-Control-Request-Method is not allowed' do
|
111
299
|
preflight_request('http://localhost:3000', '/get-only', :method => :post)
|
112
|
-
|
300
|
+
last_response.wont_render_cors_success
|
301
|
+
cors_result.miss_reason.must_equal Rack::Cors::Result::MISS_DENY_METHOD
|
302
|
+
cors_result.wont_be :hit
|
303
|
+
cors_result.must_be :preflight
|
113
304
|
end
|
114
305
|
|
115
|
-
should
|
306
|
+
it 'should fail if header is not allowed' do
|
116
307
|
preflight_request('http://localhost:3000', '/single_header', :headers => 'Fooey')
|
117
|
-
|
308
|
+
last_response.wont_render_cors_success
|
309
|
+
cors_result.miss_reason.must_equal Rack::Cors::Result::MISS_DENY_HEADER
|
310
|
+
cors_result.wont_be :hit
|
311
|
+
cors_result.must_be :preflight
|
118
312
|
end
|
119
313
|
|
120
|
-
should
|
314
|
+
it 'should allow any header if headers = :any' do
|
121
315
|
preflight_request('http://localhost:3000', '/', :headers => 'Fooey')
|
122
|
-
|
316
|
+
last_response.must_render_cors_success
|
317
|
+
end
|
318
|
+
|
319
|
+
it 'should allow any method if methods = :any' do
|
320
|
+
preflight_request('http://localhost:3000', '/', :methods => :any)
|
321
|
+
last_response.must_render_cors_success
|
123
322
|
end
|
124
323
|
|
125
|
-
|
324
|
+
it 'allows PATCH method' do
|
325
|
+
preflight_request('http://localhost:3000', '/', :methods => [ :patch ])
|
326
|
+
last_response.must_render_cors_success
|
327
|
+
end
|
328
|
+
|
329
|
+
it 'should allow header case insensitive match' do
|
126
330
|
preflight_request('http://localhost:3000', '/single_header', :headers => 'X-Domain-Token')
|
127
|
-
|
331
|
+
last_response.must_render_cors_success
|
128
332
|
end
|
129
333
|
|
130
|
-
should
|
334
|
+
it 'should allow multiple headers match' do
|
131
335
|
# Webkit style
|
132
336
|
preflight_request('http://localhost:3000', '/two_headers', :headers => 'X-Requested-With, X-Domain-Token')
|
133
|
-
|
337
|
+
last_response.must_render_cors_success
|
134
338
|
|
135
339
|
# Gecko style
|
136
340
|
preflight_request('http://localhost:3000', '/two_headers', :headers => 'x-requested-with,x-domain-token')
|
137
|
-
|
341
|
+
last_response.must_render_cors_success
|
138
342
|
end
|
139
343
|
|
140
|
-
should
|
344
|
+
it 'should * origin should allow any origin' do
|
141
345
|
preflight_request('http://locohost:3000', '/public')
|
142
|
-
|
143
|
-
|
346
|
+
last_response.must_render_cors_success
|
347
|
+
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
|
144
348
|
end
|
145
349
|
|
146
|
-
should
|
350
|
+
it 'should * origin should allow any origin, and set * if no credentials required' do
|
147
351
|
preflight_request('http://locohost:3000', '/public_without_credentials')
|
148
|
-
|
149
|
-
|
352
|
+
last_response.must_render_cors_success
|
353
|
+
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
|
354
|
+
end
|
355
|
+
|
356
|
+
it 'should return "file://" as header with "file://" as origin' do
|
357
|
+
preflight_request('file://', '/')
|
358
|
+
last_response.must_render_cors_success
|
359
|
+
last_response.headers['Access-Control-Allow-Origin'].must_equal 'file://'
|
360
|
+
end
|
361
|
+
|
362
|
+
describe '' do
|
363
|
+
|
364
|
+
let(:app) do
|
365
|
+
test = self
|
366
|
+
Rack::Builder.new do
|
367
|
+
use CaptureResult, holder: test
|
368
|
+
use Rack::Cors, debug: true, logger: Logger.new(StringIO.new) do
|
369
|
+
allow do
|
370
|
+
origins '*'
|
371
|
+
resource '/', :methods => :post
|
372
|
+
end
|
373
|
+
end
|
374
|
+
map('/') do
|
375
|
+
run ->(env) { [500, {}, ['FAIL!']] }
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
it "should not send failed preflight requests thru the app" do
|
381
|
+
preflight_request('http://localhost', '/', :method => :unsupported)
|
382
|
+
last_response.wont_render_cors_success
|
383
|
+
last_response.status.must_equal 200
|
384
|
+
cors_result.must_be :preflight
|
385
|
+
cors_result.wont_be :hit
|
386
|
+
cors_result.miss_reason.must_equal Rack::Cors::Result::MISS_DENY_METHOD
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
describe "with insecure configuration" do
|
392
|
+
let(:app) { load_app('insecure') }
|
393
|
+
|
394
|
+
it "should raise an error" do
|
395
|
+
proc { cors_request '/public' }.must_raise Rack::Cors::Resource::CorsMisconfigurationError
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
describe "with non HTTP config" do
|
400
|
+
let(:app) { load_app("non_http") }
|
401
|
+
|
402
|
+
it 'should support non http/https origins' do
|
403
|
+
successful_cors_request '/public', origin: 'content://com.company.app'
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
describe 'Rack::Lint' do
|
408
|
+
def app
|
409
|
+
@app ||= Rack::Builder.new do
|
410
|
+
use Rack::Cors
|
411
|
+
use Rack::Lint
|
412
|
+
run ->(env) { [200, {'Content-Type' => 'text/html'}, ['hello']] }
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
it 'is lint-compliant with non-CORS request' do
|
417
|
+
get '/'
|
418
|
+
last_response.status.must_equal 200
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
describe 'with app overriding CORS header' do
|
423
|
+
let(:app) do
|
424
|
+
Rack::Builder.new do
|
425
|
+
use Rack::Cors, debug: true, logger: Logger.new(StringIO.new) do
|
426
|
+
allow do
|
427
|
+
origins '*'
|
428
|
+
resource '/'
|
429
|
+
end
|
430
|
+
end
|
431
|
+
map('/') do
|
432
|
+
run ->(env) { [200, {'Access-Control-Allow-Origin' => 'http://foo.net'}, ['success']] }
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
it "should return app header" do
|
438
|
+
successful_cors_request origin: "http://example.net"
|
439
|
+
last_response.headers['Access-Control-Allow-Origin'].must_equal "http://foo.net"
|
440
|
+
end
|
441
|
+
|
442
|
+
it "should return original headers if in debug" do
|
443
|
+
successful_cors_request origin: "http://example.net"
|
444
|
+
last_response.headers['X-Rack-CORS-Original-Access-Control-Allow-Origin'].must_equal "*"
|
150
445
|
end
|
446
|
+
end
|
151
447
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
448
|
+
describe 'with headers set to nil' do
|
449
|
+
let(:app) do
|
450
|
+
Rack::Builder.new do
|
451
|
+
use Rack::Cors do
|
452
|
+
allow do
|
453
|
+
origins '*'
|
454
|
+
resource '/', headers: nil
|
455
|
+
end
|
456
|
+
end
|
457
|
+
map('/') do
|
458
|
+
run ->(env) { [200, {'Content-Type' => 'text/html'}, ['hello']] }
|
459
|
+
end
|
460
|
+
end
|
156
461
|
end
|
157
462
|
|
158
|
-
|
159
|
-
preflight_request('http://localhost:3000', '/')
|
160
|
-
|
161
|
-
|
463
|
+
it 'should succeed with CORS simple headers' do
|
464
|
+
preflight_request('http://localhost:3000', '/', :headers => 'Accept')
|
465
|
+
last_response.must_render_cors_success
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
describe 'with custom allowed headers' do
|
470
|
+
let(:app) do
|
471
|
+
Rack::Builder.new do
|
472
|
+
use Rack::Cors do
|
473
|
+
allow do
|
474
|
+
origins '*'
|
475
|
+
resource '/', headers: []
|
476
|
+
end
|
477
|
+
end
|
478
|
+
map('/') do
|
479
|
+
run ->(env) { [200, {'Content-Type' => 'text/html'}, ['hello']] }
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
it 'should succeed with CORS simple headers' do
|
485
|
+
preflight_request('http://localhost:3000', '/', :headers => 'Accept')
|
486
|
+
last_response.must_render_cors_success
|
487
|
+
preflight_request('http://localhost:3000', '/', :headers => 'Accept-Language')
|
488
|
+
last_response.must_render_cors_success
|
489
|
+
preflight_request('http://localhost:3000', '/', :headers => 'Content-Type')
|
490
|
+
last_response.must_render_cors_success
|
491
|
+
preflight_request('http://localhost:3000', '/', :headers => 'Content-Language')
|
492
|
+
last_response.must_render_cors_success
|
162
493
|
end
|
163
494
|
end
|
164
495
|
|
@@ -170,8 +501,12 @@ class CorsTest < Test::Unit::TestCase
|
|
170
501
|
opts.merge! args.last if args.last.is_a?(Hash)
|
171
502
|
|
172
503
|
header 'Origin', opts[:origin]
|
173
|
-
current_session.__send__ opts[:method], path
|
174
|
-
|
504
|
+
current_session.__send__ opts[:method], path, {}, test: self
|
505
|
+
end
|
506
|
+
|
507
|
+
def successful_cors_request(*args)
|
508
|
+
cors_request(*args)
|
509
|
+
last_response.must_render_cors_success
|
175
510
|
end
|
176
511
|
|
177
512
|
def preflight_request(origin, path, opts = {})
|
@@ -184,12 +519,4 @@ class CorsTest < Test::Unit::TestCase
|
|
184
519
|
end
|
185
520
|
options path
|
186
521
|
end
|
187
|
-
|
188
|
-
def assert_cors_success
|
189
|
-
assert_not_nil last_response.headers['Access-Control-Allow-Origin'], 'missing Access-Control-Allow-Origin header'
|
190
|
-
end
|
191
|
-
|
192
|
-
def assert_cors_failure
|
193
|
-
assert_nil last_response.headers['Access-Control-Allow-Origin'], 'no expecting Access-Control-Allow-Origin header'
|
194
|
-
end
|
195
522
|
end
|