rack-cors 0.4.1 → 2.0.1

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.

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