rack-cors 0.3.1 → 0.4.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.
- 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
|