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 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