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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0e212c45ff98679f235b713d3ae97074667cd4af
4
- data.tar.gz: aacdef32fae6abecbfca418af44cb2d2f0f838c6
3
+ metadata.gz: 7dff10b0f517624773d305e553e1f6720dc576ac
4
+ data.tar.gz: 0919837eb9b79cedeb5c638ae9e9169ae4587dd6
5
5
  SHA512:
6
- metadata.gz: 99702620fc7e5af96a9d340a6715dc9e56823d7e5dc48273cbc7fbba219d4ce7c637916f9b92f6b19cf9bf31c302bd55f0ec5d82889d9f31c4c593cfb6cc1462
7
- data.tar.gz: 361ac5ff10a838811532d36351bdea83dd30a7551f757cdc3b717b6725545ad42866c186e06c721b90292cd9e3d496548112880a08cd71f61b5f2557287aab19
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 an array): The HTTP headers in the resource response can can be exposed to the client.
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 RAILS_ENV=production rake middleware
114
+ RAILS_ENV=production bundle exec rake middleware
113
115
  ```
@@ -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['HTTP_X_ORIGIN']
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
- # http://www.w3.org/TR/cors/#resource-implementation
78
- unless headers['Access-Control-Allow-Origin'] == '*'
79
- vary = headers['Vary']
80
- headers['Vary'] = ((vary ? vary.split(/,\s*/) : []) + ['Origin']).uniq.join(', ')
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 = find_resource(env)
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 = find_resource(env)
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 find_resource(env)
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.find_resource(path)
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 find_resource(path)
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 = path
270
- self.methods = prepare_and_validate_methods_option(opts[:methods]) || [:get]
271
- self.credentials = opts[:credentials].nil? ? true : opts[:credentials]
272
- self.max_age = opts[:max_age] || 1728000
273
- self.pattern = compile(path)
274
- @public_resource = public_resource
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 match?(path)
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.to_sym)
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
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class Cors
3
- VERSION = "0.3.1"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
@@ -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 not add Vary header if Access-Control-Allow-Origin header was added and if it is generic (*)' do
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'].must_be_nil
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'].must_be_nil
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
- describe 'logging' do
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 : '/'
@@ -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.3.1
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: 2014-12-27 00:00:00.000000000 Z
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.2.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