rack-cors 0.3.1 → 0.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG +11 -0
- data/README.md +4 -2
- data/lib/rack/cors.rb +60 -25
- data/lib/rack/cors/version.rb +1 -1
- data/test/unit/cors_test.rb +47 -4
- data/test/unit/test.ru +4 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7dff10b0f517624773d305e553e1f6720dc576ac
|
4
|
+
data.tar.gz: 0919837eb9b79cedeb5c638ae9e9169ae4587dd6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: acf2fa484542085cd98d4e4eafd9a62d9bf38a156e67015829c1c58ba8b899866afb6489f5202122ad2f7b4f70e9bd0c69253590dfb7c52c43095168d67f79bd
|
7
|
+
data.tar.gz: 3b40c2862c728310236da3070f061b040c8090068142ffe0decdd16d66f3cdf83e4520ccf07c6cde38bb43b95cd8e7d38d6f4cbb6a1e4d06d4eb2324901ff531
|
data/CHANGELOG
CHANGED
@@ -1,6 +1,17 @@
|
|
1
1
|
# Change Log
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
3
|
|
4
|
+
## 0.4.0 - 2015-04-15
|
5
|
+
### Changed
|
6
|
+
- Don't set HTTP_ORIGIN with HTTP_X_ORIGIN if nil
|
7
|
+
### Added
|
8
|
+
- Calculate vary headers for non-CORS resources
|
9
|
+
- Support custom vary headers for resource
|
10
|
+
- Support :if option for resource
|
11
|
+
- Support :any as a possible value for :methods option
|
12
|
+
### Fixed
|
13
|
+
- Don't symbolize incoming HTTP request methods
|
14
|
+
|
4
15
|
## 0.3.1 - 2014-12-27
|
5
16
|
### Changed
|
6
17
|
- Changed the env key to rack.cors to avoid Rack::Lint warnings
|
data/README.md
CHANGED
@@ -83,9 +83,11 @@ A Resource path can be specified as exact string match (`/path/to/file.txt`) or
|
|
83
83
|
|
84
84
|
* **methods** (string or array): The HTTP methods allowed for the resource.
|
85
85
|
* **headers** (string or array or `:any`): The HTTP headers that will be allowed in the CORS resource request. Use `:any` to allow for any headers in the actual request.
|
86
|
-
* **expose** (string or
|
86
|
+
* **expose** (string or array): The HTTP headers in the resource response can can be exposed to the client.
|
87
87
|
* **credentials** (boolean): Sets the `Access-Control-Allow-Credentials` response header.
|
88
88
|
* **max_age** (number): Sets the `Access-Control-Max-Age` response header.
|
89
|
+
* **if** (Proc): If the result of the proc is true, will process the request as a valid CORS request.
|
90
|
+
* **vary** (string or array): A list of HTTP headers to add to the 'Vary' header.
|
89
91
|
|
90
92
|
|
91
93
|
## Common Gotchas
|
@@ -109,5 +111,5 @@ bundle exec rake middleware
|
|
109
111
|
In many cases, the Rack stack will be different running in production environments. For example, the `ActionDispatch::Static` middleware will not be part of the stack if `config.serve_static_assets = false`. You can run the following command to see what your middleware stack looks like in production:
|
110
112
|
|
111
113
|
```bash
|
112
|
-
bundle exec
|
114
|
+
RAILS_ENV=production bundle exec rake middleware
|
113
115
|
```
|
data/lib/rack/cors.rb
CHANGED
@@ -5,7 +5,10 @@ module Rack
|
|
5
5
|
ENV_KEY = 'rack.cors'.freeze
|
6
6
|
|
7
7
|
ORIGIN_HEADER_KEY = 'HTTP_ORIGIN'.freeze
|
8
|
+
ORIGIN_X_HEADER_KEY = 'HTTP_X_ORIGIN'.freeze
|
8
9
|
PATH_INFO_HEADER_KEY = 'PATH_INFO'.freeze
|
10
|
+
VARY_HEADER_KEY = 'Vary'.freeze
|
11
|
+
DEFAULT_VARY_HEADERS = ['Origin'].freeze
|
9
12
|
|
10
13
|
def initialize(app, opts={}, &block)
|
11
14
|
@app = app
|
@@ -43,7 +46,7 @@ module Rack
|
|
43
46
|
end
|
44
47
|
|
45
48
|
def call(env)
|
46
|
-
env[ORIGIN_HEADER_KEY] ||= env[
|
49
|
+
env[ORIGIN_HEADER_KEY] ||= env[ORIGIN_X_HEADER_KEY] if env[ORIGIN_X_HEADER_KEY]
|
47
50
|
|
48
51
|
add_headers = nil
|
49
52
|
if env[ORIGIN_HEADER_KEY]
|
@@ -69,16 +72,28 @@ module Rack
|
|
69
72
|
Result.miss(env, Result::MISS_NO_ORIGIN)
|
70
73
|
end
|
71
74
|
|
75
|
+
# This call must be done BEFORE calling the app because for some reason
|
76
|
+
# env[PATH_INFO_HEADER_KEY] gets changed after that and it won't match.
|
77
|
+
# (At least in rails 4.1.6)
|
78
|
+
vary_resource = resource_for_path(env[PATH_INFO_HEADER_KEY])
|
79
|
+
|
72
80
|
status, headers, body = @app.call env
|
73
81
|
|
74
82
|
if add_headers
|
75
83
|
headers = headers.merge(add_headers)
|
84
|
+
end
|
76
85
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
86
|
+
# Vary header should ALWAYS mention Origin if there's ANY chance for the
|
87
|
+
# response to be different depending on the Origin header value.
|
88
|
+
# Better explained here: http://www.fastly.com/blog/best-practices-for-using-the-vary-header/
|
89
|
+
if vary_resource
|
90
|
+
vary = headers[VARY_HEADER_KEY]
|
91
|
+
cors_vary_headers = if vary_resource.vary_headers && vary_resource.vary_headers.any?
|
92
|
+
vary_resource.vary_headers
|
93
|
+
else
|
94
|
+
DEFAULT_VARY_HEADERS
|
81
95
|
end
|
96
|
+
headers[VARY_HEADER_KEY] = ((vary ? vary.split(/,\s*/) : []) + cors_vary_headers).uniq.join(', ')
|
82
97
|
end
|
83
98
|
|
84
99
|
if debug? && result = env[ENV_KEY]
|
@@ -115,7 +130,7 @@ module Rack
|
|
115
130
|
end
|
116
131
|
|
117
132
|
def process_preflight(env)
|
118
|
-
resource, error =
|
133
|
+
resource, error = match_resource(env)
|
119
134
|
if resource
|
120
135
|
Result.preflight_hit(env)
|
121
136
|
preflight = resource.process_preflight(env)
|
@@ -128,7 +143,7 @@ module Rack
|
|
128
143
|
end
|
129
144
|
|
130
145
|
def process_cors(env)
|
131
|
-
resource, error =
|
146
|
+
resource, error = match_resource(env)
|
132
147
|
if resource
|
133
148
|
Result.hit(env)
|
134
149
|
cors = resource.to_headers(env)
|
@@ -140,7 +155,16 @@ module Rack
|
|
140
155
|
end
|
141
156
|
end
|
142
157
|
|
143
|
-
def
|
158
|
+
def resource_for_path(path_info)
|
159
|
+
all_resources.each do |r|
|
160
|
+
if found = r.resource_for_path(path_info)
|
161
|
+
return found
|
162
|
+
end
|
163
|
+
end
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
|
167
|
+
def match_resource(env)
|
144
168
|
path = env[PATH_INFO_HEADER_KEY]
|
145
169
|
origin = env[ORIGIN_HEADER_KEY]
|
146
170
|
|
@@ -148,7 +172,7 @@ module Rack
|
|
148
172
|
all_resources.each do |r|
|
149
173
|
if r.allow_origin?(origin, env)
|
150
174
|
origin_matched = true
|
151
|
-
if found = r.
|
175
|
+
if found = r.match_resource(path, env)
|
152
176
|
return [found, nil]
|
153
177
|
end
|
154
178
|
end
|
@@ -257,21 +281,27 @@ module Rack
|
|
257
281
|
end
|
258
282
|
end
|
259
283
|
|
260
|
-
def
|
261
|
-
@resources.detect{|r| r.match?(path)}
|
284
|
+
def match_resource(path, env)
|
285
|
+
@resources.detect { |r| r.match?(path, env) }
|
262
286
|
end
|
287
|
+
|
288
|
+
def resource_for_path(path)
|
289
|
+
@resources.detect { |r| r.matches_path?(path) }
|
290
|
+
end
|
291
|
+
|
263
292
|
end
|
264
293
|
|
265
294
|
class Resource
|
266
|
-
attr_accessor :path, :methods, :headers, :expose, :max_age, :credentials, :pattern
|
295
|
+
attr_accessor :path, :methods, :headers, :expose, :max_age, :credentials, :pattern, :if_proc, :vary_headers
|
267
296
|
|
268
297
|
def initialize(public_resource, path, opts={})
|
269
|
-
self.path
|
270
|
-
self.
|
271
|
-
self.
|
272
|
-
self.
|
273
|
-
self.
|
274
|
-
|
298
|
+
self.path = path
|
299
|
+
self.credentials = opts[:credentials].nil? ? true : opts[:credentials]
|
300
|
+
self.max_age = opts[:max_age] || 1728000
|
301
|
+
self.pattern = compile(path)
|
302
|
+
self.if_proc = opts[:if]
|
303
|
+
self.vary_headers = opts[:vary] && [opts[:vary]].flatten
|
304
|
+
@public_resource = public_resource
|
275
305
|
|
276
306
|
self.headers = case opts[:headers]
|
277
307
|
when :any then :any
|
@@ -280,13 +310,23 @@ module Rack
|
|
280
310
|
[opts[:headers]].flatten.collect{|h| h.downcase}
|
281
311
|
end
|
282
312
|
|
313
|
+
self.methods = case opts[:methods]
|
314
|
+
when :any then [:get, :head, :post, :put, :patch, :delete, :options]
|
315
|
+
else
|
316
|
+
ensure_enum(opts[:methods]) || [:get]
|
317
|
+
end.map{|e| e.to_s }
|
318
|
+
|
283
319
|
self.expose = opts[:expose] ? [opts[:expose]].flatten : nil
|
284
320
|
end
|
285
321
|
|
286
|
-
def
|
322
|
+
def matches_path?(path)
|
287
323
|
pattern =~ path
|
288
324
|
end
|
289
325
|
|
326
|
+
def match?(path, env)
|
327
|
+
matches_path?(path) && (if_proc.nil? || if_proc.call(env))
|
328
|
+
end
|
329
|
+
|
290
330
|
def process_preflight(env)
|
291
331
|
return nil if invalid_method_request?(env) || invalid_headers_request?(env)
|
292
332
|
{'Content-Type' => 'text/plain'}.merge(to_preflight_headers(env))
|
@@ -323,7 +363,7 @@ module Rack
|
|
323
363
|
|
324
364
|
def invalid_method_request?(env)
|
325
365
|
request_method = env['HTTP_ACCESS_CONTROL_REQUEST_METHOD']
|
326
|
-
request_method.nil? || !methods.include?(request_method.downcase
|
366
|
+
request_method.nil? || !methods.include?(request_method.downcase)
|
327
367
|
end
|
328
368
|
|
329
369
|
def invalid_headers_request?(env)
|
@@ -339,11 +379,6 @@ module Rack
|
|
339
379
|
end
|
340
380
|
end
|
341
381
|
|
342
|
-
def prepare_and_validate_methods_option setting
|
343
|
-
raise ArgumentError.new("rack-cors methods must be an single HTTP verb, or an array of verbs") if setting && setting == :any
|
344
|
-
ensure_enum setting
|
345
|
-
end
|
346
|
-
|
347
382
|
def ensure_enum(v)
|
348
383
|
return nil if v.nil?
|
349
384
|
[v].flatten
|
data/lib/rack/cors/version.rb
CHANGED
data/test/unit/cors_test.rb
CHANGED
@@ -75,16 +75,28 @@ describe Rack::Cors do
|
|
75
75
|
last_response.headers['Access-Control-Expose-Headers'].must_equal 'expose-test-1, expose-test-2'
|
76
76
|
end
|
77
77
|
|
78
|
+
# Explanation: http://www.fastly.com/blog/best-practices-for-using-the-vary-header/
|
79
|
+
it "should add Vary header if resource matches even if Origin header isn't present" do
|
80
|
+
get '/'
|
81
|
+
should_render_cors_failure
|
82
|
+
last_response.headers['Vary'].must_equal 'Origin'
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should add Vary header based on :vary option" do
|
86
|
+
cors_request '/vary_test'
|
87
|
+
last_response.headers['Vary'].must_equal 'Origin, Host'
|
88
|
+
end
|
89
|
+
|
78
90
|
it 'should add Vary header if Access-Control-Allow-Origin header was added and if it is specific' do
|
79
91
|
cors_request '/', :origin => "http://192.168.0.3:8080"
|
80
92
|
last_response.headers['Access-Control-Allow-Origin'].must_equal 'http://192.168.0.3:8080'
|
81
93
|
last_response.headers['Vary'].wont_be_nil
|
82
94
|
end
|
83
95
|
|
84
|
-
it 'should
|
96
|
+
it 'should add Vary header even if Access-Control-Allow-Origin header was added and it is generic (*)' do
|
85
97
|
cors_request '/public_without_credentials', :origin => "http://192.168.1.3:8080"
|
86
98
|
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
|
87
|
-
last_response.headers['Vary'].
|
99
|
+
last_response.headers['Vary'].must_equal 'Origin'
|
88
100
|
end
|
89
101
|
|
90
102
|
it 'should support multi allow configurations for the same resource' do
|
@@ -94,7 +106,7 @@ describe Rack::Cors do
|
|
94
106
|
|
95
107
|
cors_request '/multi-allow-config', :origin => "http://192.168.1.3:8080"
|
96
108
|
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
|
97
|
-
last_response.headers['Vary'].
|
109
|
+
last_response.headers['Vary'].must_equal 'Origin'
|
98
110
|
end
|
99
111
|
|
100
112
|
it "should not return CORS headers on OPTIONS request if Access-Control-Allow-Origin is not present" do
|
@@ -102,7 +114,18 @@ describe Rack::Cors do
|
|
102
114
|
last_response.headers['Access-Control-Allow-Origin'].must_be_nil
|
103
115
|
end
|
104
116
|
|
105
|
-
|
117
|
+
it "should not apply CORS headers if it does not match conditional on resource" do
|
118
|
+
header 'Origin', 'http://192.168.0.1:1234'
|
119
|
+
get '/conditional'
|
120
|
+
should_render_cors_failure
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should apply CORS headers if it does match conditional on resource" do
|
124
|
+
header 'X-OK', '1'
|
125
|
+
cors_request '/conditional', :origin => 'http://192.168.0.1:1234'
|
126
|
+
end
|
127
|
+
|
128
|
+
describe 'logging' do
|
106
129
|
it 'should not log debug messages if debug option is false' do
|
107
130
|
app = mock
|
108
131
|
app.stubs(:call).returns(200, {}, [''])
|
@@ -190,6 +213,11 @@ describe Rack::Cors do
|
|
190
213
|
should_render_cors_success
|
191
214
|
end
|
192
215
|
|
216
|
+
it 'should allow any method if methods = :any' do
|
217
|
+
preflight_request('http://localhost:3000', '/', :methods => :any)
|
218
|
+
should_render_cors_success
|
219
|
+
end
|
220
|
+
|
193
221
|
it 'should allow header case insensitive match' do
|
194
222
|
preflight_request('http://localhost:3000', '/single_header', :headers => 'X-Domain-Token')
|
195
223
|
should_render_cors_success
|
@@ -244,6 +272,21 @@ describe Rack::Cors do
|
|
244
272
|
end
|
245
273
|
end
|
246
274
|
|
275
|
+
describe 'Rack::Lint' do
|
276
|
+
def app
|
277
|
+
@app ||= Rack::Builder.new do
|
278
|
+
use Rack::Cors
|
279
|
+
use Rack::Lint
|
280
|
+
run ->(env) { [200, {'Content-Type' => 'text/html'}, ['hello']] }
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
it 'is lint-compliant with non-CORS request' do
|
285
|
+
get '/'
|
286
|
+
last_response.status.must_equal 200
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
247
290
|
protected
|
248
291
|
def cors_request(*args)
|
249
292
|
path = args.first.is_a?(String) ? args.first : '/'
|
data/test/unit/test.ru
CHANGED
@@ -1,17 +1,20 @@
|
|
1
1
|
require 'rack/cors'
|
2
2
|
|
3
3
|
#use Rack::Cors, :debug => true, :logger => ::Logger.new(STDOUT) do
|
4
|
+
use Rack::Lint
|
4
5
|
use Rack::Cors do
|
5
6
|
allow do
|
6
7
|
origins 'localhost:3000', '127.0.0.1:3000', /http:\/\/192\.168\.0\.\d{1,3}(:\d+)?/, 'file://'
|
7
8
|
|
8
9
|
resource '/get-only', :methods => :get
|
9
|
-
resource '/', :headers => :any
|
10
|
+
resource '/', :headers => :any, :methods => :any
|
10
11
|
resource '/options', :methods => :options
|
11
12
|
resource '/single_header', :headers => 'x-domain-token'
|
12
13
|
resource '/two_headers', :headers => %w{x-domain-token x-requested-with}
|
13
14
|
resource '/expose_single_header', :expose => 'expose-test'
|
14
15
|
resource '/expose_multiple_headers', :expose => %w{expose-test-1 expose-test-2}
|
16
|
+
resource '/conditional', :methods => :get, :if => proc { |env| !!env['HTTP_X_OK'] }
|
17
|
+
resource '/vary_test', :methods => :get, :vary => %w{ Origin Host }
|
15
18
|
# resource '/file/at/*',
|
16
19
|
# :methods => [:get, :post, :put, :delete],
|
17
20
|
# :headers => :any,
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-cors
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Calvin Yu
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-04-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -127,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
127
|
version: '0'
|
128
128
|
requirements: []
|
129
129
|
rubyforge_project:
|
130
|
-
rubygems_version: 2.
|
130
|
+
rubygems_version: 2.4.5
|
131
131
|
signing_key:
|
132
132
|
specification_version: 4
|
133
133
|
summary: Middleware for enabling Cross-Origin Resource Sharing in Rack apps
|