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.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yaml +39 -0
- data/.rubocop.yml +31 -0
- data/CHANGELOG.md +99 -0
- data/Gemfile +3 -1
- data/README.md +68 -43
- data/Rakefile +5 -4
- data/lib/rack/cors/resource.rb +142 -0
- data/lib/rack/cors/resources/cors_misconfiguration_error.rb +14 -0
- data/lib/rack/cors/resources.rb +62 -0
- data/lib/rack/cors/result.rb +63 -0
- data/lib/rack/cors/version.rb +3 -1
- data/lib/rack/cors.rb +124 -323
- data/rack-cors.gemspec +20 -16
- data/test/.rubocop.yml +8 -0
- data/test/cors/test.cors.coffee +9 -2
- data/test/cors/test.cors.js +22 -10
- data/test/unit/cors_test.rb +303 -120
- data/test/unit/dsl_test.rb +38 -26
- data/test/unit/insecure.ru +10 -0
- data/test/unit/non_http.ru +2 -0
- data/test/unit/test.ru +34 -18
- metadata +82 -27
- data/.travis.yml +0 -6
- data/CHANGELOG +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f47b5b2ba34721795ddb1c65e70e989134655e7f47116dd977edee702a79f41f
|
4
|
+
data.tar.gz: 7c03dc701b00b7418ab4d733872fccc3522392f0f85014b8ca25045767d866a9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5a94b8f282fd5367e125f4b49e18ce5fb1c07581d89b2d50dc8cf4eb70e8404d97c78a6ccaf90f1e41f0f220bb09ff9dd07a4677559935cc35256674e6c512d
|
7
|
+
data.tar.gz: bee187e2dc53281d8b454df32b6a8fd50e05b66de4f25649d74f176abf29d52eb4cee5a2b2e602e087c5f5805b6dd55ab86a9e0692d3d82c5538e0c30e3c020b
|
@@ -0,0 +1,39 @@
|
|
1
|
+
name: ci
|
2
|
+
|
3
|
+
on:
|
4
|
+
- push
|
5
|
+
- pull_request
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
test:
|
9
|
+
strategy:
|
10
|
+
fail-fast: false
|
11
|
+
matrix:
|
12
|
+
ruby:
|
13
|
+
- "2.3"
|
14
|
+
- "2.4"
|
15
|
+
- "2.5"
|
16
|
+
- "2.6"
|
17
|
+
- "2.7"
|
18
|
+
- "3.0"
|
19
|
+
- "3.1"
|
20
|
+
- "3.2"
|
21
|
+
- truffleruby-head
|
22
|
+
runs-on: ubuntu-latest
|
23
|
+
steps:
|
24
|
+
- uses: actions/checkout@v3
|
25
|
+
- uses: ruby/setup-ruby@v1
|
26
|
+
with:
|
27
|
+
ruby-version: ${{ matrix.ruby }}
|
28
|
+
bundler-cache: true
|
29
|
+
- run: bundle exec rake test
|
30
|
+
|
31
|
+
rubocop:
|
32
|
+
runs-on: ubuntu-latest
|
33
|
+
steps:
|
34
|
+
- uses: actions/checkout@v3
|
35
|
+
- uses: ruby/setup-ruby@v1
|
36
|
+
with:
|
37
|
+
ruby-version: 3.2.1
|
38
|
+
bundler-cache: true
|
39
|
+
- run: bundle exec rubocop
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
---
|
2
|
+
AllCops:
|
3
|
+
Exclude:
|
4
|
+
- "examples/**/*"
|
5
|
+
- "vendor/**/*"
|
6
|
+
|
7
|
+
# Disables
|
8
|
+
Layout/LineLength:
|
9
|
+
Enabled: false
|
10
|
+
Style/Documentation:
|
11
|
+
Enabled: false
|
12
|
+
Metrics/ClassLength:
|
13
|
+
Enabled: false
|
14
|
+
Metrics/MethodLength:
|
15
|
+
Enabled: false
|
16
|
+
Metrics/BlockLength:
|
17
|
+
Enabled: false
|
18
|
+
Style/HashEachMethods:
|
19
|
+
Enabled: false
|
20
|
+
Style/HashTransformKeys:
|
21
|
+
Enabled: false
|
22
|
+
Style/HashTransformValues:
|
23
|
+
Enabled: false
|
24
|
+
Style/DoubleNegation:
|
25
|
+
Enabled: false
|
26
|
+
Metrics/CyclomaticComplexity:
|
27
|
+
Enabled: false
|
28
|
+
Metrics/PerceivedComplexity:
|
29
|
+
Enabled: false
|
30
|
+
Metrics/AbcSize:
|
31
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# Change Log
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
## 2.0.1 - 2023-02-17
|
5
|
+
### Changed
|
6
|
+
- Use Rack::Utils::HeaderHash when Rack 2.x is detected
|
7
|
+
|
8
|
+
## 2.0.0 - 2023-02-14
|
9
|
+
### Changed
|
10
|
+
- Refactored codebase
|
11
|
+
- Support declaring custom protocols in origin
|
12
|
+
- Lowercased header names as defined by Rack spec
|
13
|
+
- Fix issue with duplicate headers because of header name case
|
14
|
+
|
15
|
+
## 1.1.1 - 2019-12-29
|
16
|
+
### Changed
|
17
|
+
- Allow /<resource>/* to match /<resource>/ and /<resource> paths
|
18
|
+
|
19
|
+
## 1.1.0 - 2019-11-19
|
20
|
+
### Changed
|
21
|
+
- Use Rack::Utils.escape_path instead of Rack::Utils.escape
|
22
|
+
- Require Rack 2.0 for escape_path method
|
23
|
+
- Don't try to clean path if invalid.
|
24
|
+
- Return 400 (Bad Request) on preflights with invalid path
|
25
|
+
|
26
|
+
## 1.0.6 - 2019-11-14
|
27
|
+
### Changed
|
28
|
+
- Use Rack::Utils.escape to make compat with Rack 1.6.0
|
29
|
+
|
30
|
+
## 1.0.5 - 2019-11-14
|
31
|
+
### Changed
|
32
|
+
- Update Gem spec to require rack >= 1.6.0
|
33
|
+
|
34
|
+
## 1.0.4 - 2019-11-13
|
35
|
+
### Security
|
36
|
+
- Escape and resolve path before evaluating resource rules (thanks to Colby Morgan)
|
37
|
+
|
38
|
+
## 1.0.3 - 2019-03-24
|
39
|
+
### Changed
|
40
|
+
- Don't send 'Content-Type' header with pre-flight requests
|
41
|
+
- Allow ruby array for vary header config
|
42
|
+
|
43
|
+
## 1.0.2 - 2017-10-22
|
44
|
+
### Fixed
|
45
|
+
- Automatically allow simple headers when headers are set
|
46
|
+
|
47
|
+
## 1.0.1 - 2017-07-18
|
48
|
+
### Fixed
|
49
|
+
- Allow lambda origin configuration
|
50
|
+
|
51
|
+
## 1.0.0 - 2017-07-15
|
52
|
+
### Security
|
53
|
+
- Don't implicitly accept 'null' origins when 'file://' is specified
|
54
|
+
(https://github.com/cyu/rack-cors/pull/134)
|
55
|
+
- Ignore '' origins (https://github.com/cyu/rack-cors/issues/139)
|
56
|
+
- Default credentials option on resources to false
|
57
|
+
(https://github.com/cyu/rack-cors/issues/95)
|
58
|
+
- Don't allow credentials option to be true if '*' is specified is origin
|
59
|
+
(https://github.com/cyu/rack-cors/pull/142)
|
60
|
+
- Don't reflect Origin header when '*' is specified as origin
|
61
|
+
(https://github.com/cyu/rack-cors/pull/142)
|
62
|
+
|
63
|
+
### Fixed
|
64
|
+
- Don't respond immediately on non-matching preflight requests instead of
|
65
|
+
sending them through the app (https://github.com/cyu/rack-cors/pull/106)
|
66
|
+
|
67
|
+
## 0.4.1 - 2017-02-01
|
68
|
+
### Fixed
|
69
|
+
- Return miss result in X-Rack-CORS instead of incorrectly returning
|
70
|
+
preflight-hit
|
71
|
+
|
72
|
+
## 0.4.0 - 2015-04-15
|
73
|
+
### Changed
|
74
|
+
- Don't set HTTP_ORIGIN with HTTP_X_ORIGIN if nil
|
75
|
+
|
76
|
+
### Added
|
77
|
+
- Calculate vary headers for non-CORS resources
|
78
|
+
- Support custom vary headers for resource
|
79
|
+
- Support :if option for resource
|
80
|
+
- Support :any as a possible value for :methods option
|
81
|
+
|
82
|
+
### Fixed
|
83
|
+
- Don't symbolize incoming HTTP request methods
|
84
|
+
|
85
|
+
## 0.3.1 - 2014-12-27
|
86
|
+
### Changed
|
87
|
+
- Changed the env key to rack.cors to avoid Rack::Lint warnings
|
88
|
+
|
89
|
+
## 0.3.0 - 2014-10-19
|
90
|
+
### Added
|
91
|
+
- Added support for defining a logger with a Proc
|
92
|
+
- Return a X-Rack-CORS header when in debug mode detailing how Rack::Cors
|
93
|
+
processed a request
|
94
|
+
- Added support for non HTTP/HTTPS origins when just a domain is specified
|
95
|
+
|
96
|
+
### Changed
|
97
|
+
- Changed the log level of the fallback logger to DEBUG
|
98
|
+
- Print warning when attempting to use :any as an allowed method
|
99
|
+
- Treat incoming `Origin: null` headers as file://
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
# Rack CORS Middleware [![Build Status](https://
|
1
|
+
# Rack CORS Middleware [![Build Status](https://github.com/cyu/rack-cors/actions/workflows/ci.yaml/badge.svg)](https://github.com/cyu/rack-cors/actions)
|
2
2
|
|
3
3
|
`Rack::Cors` provides support for Cross-Origin Resource Sharing (CORS) for Rack compatible web applications.
|
4
4
|
|
5
|
-
The [CORS spec](http://www.w3.org/TR/cors/) allows web applications to make cross domain AJAX calls without using workarounds such as JSONP. See [
|
5
|
+
The [CORS spec](http://www.w3.org/TR/cors/) allows web applications to make cross domain AJAX calls without using workarounds such as JSONP. See [further explanations on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -13,49 +13,43 @@ Install the gem:
|
|
13
13
|
Or in your Gemfile:
|
14
14
|
|
15
15
|
```ruby
|
16
|
-
gem 'rack-cors'
|
16
|
+
gem 'rack-cors'
|
17
17
|
```
|
18
18
|
|
19
19
|
|
20
20
|
## Configuration
|
21
21
|
|
22
22
|
### Rails Configuration
|
23
|
-
|
23
|
+
For Rails, you'll need to add this middleware on application startup. A practical way to do this is with an initializer file. For example, the following will allow GET, POST, PATCH, or PUT requests from any origin on any resource:
|
24
24
|
|
25
25
|
```ruby
|
26
|
-
|
27
|
-
class Application < Rails::Application
|
28
|
-
|
29
|
-
# ...
|
30
|
-
|
31
|
-
# Rails 3/4
|
32
|
-
|
33
|
-
config.middleware.insert_before 0, "Rack::Cors" do
|
34
|
-
allow do
|
35
|
-
origins '*'
|
36
|
-
resource '*', :headers => :any, :methods => [:get, :post, :options]
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
# Rails 5
|
41
|
-
|
42
|
-
config.middleware.insert_before 0, Rack::Cors do
|
43
|
-
allow do
|
44
|
-
origins '*'
|
45
|
-
resource '*', :headers => :any, :methods => [:get, :post, :options]
|
46
|
-
end
|
47
|
-
end
|
26
|
+
# config/initializers/cors.rb
|
48
27
|
|
28
|
+
Rails.application.config.middleware.insert_before 0, Rack::Cors do
|
29
|
+
allow do
|
30
|
+
origins '*'
|
31
|
+
resource '*', headers: :any, methods: [:get, :post, :patch, :put]
|
49
32
|
end
|
50
33
|
end
|
51
34
|
```
|
52
|
-
|
35
|
+
|
36
|
+
NOTE: If you create application with `--api` option, configuration automatically generate in `config/initializers/cors.rb`.
|
37
|
+
|
38
|
+
We use `insert_before` to make sure `Rack::Cors` runs at the beginning of the stack to make sure it isn't interfered with by other middleware (see `Rack::Cache` note in **Common Gotchas** section). Basic setup examples for Rails 5 & Rails 6 can be found in the examples/ directory.
|
53
39
|
|
54
40
|
See The [Rails Guide to Rack](http://guides.rubyonrails.org/rails_on_rack.html) for more details on rack middlewares or watch the [railscast](http://railscasts.com/episodes/151-rack-middleware).
|
55
41
|
|
42
|
+
*Note about Rails 6*: Rails 6 has support for blocking requests from unknown hosts, so origin domains will need to be added there as well.
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
Rails.application.config.hosts << "product.com"
|
46
|
+
```
|
47
|
+
|
48
|
+
Read more about it here in the [Rails Guides](https://guides.rubyonrails.org/configuring.html#configuring-middleware)
|
49
|
+
|
56
50
|
### Rack Configuration
|
57
51
|
|
58
|
-
NOTE: If you're running Rails,
|
52
|
+
NOTE: If you're running Rails, adding `config/initializers/cors.rb` should be enough. There is no need to update `config.ru` as well.
|
59
53
|
|
60
54
|
In `config.ru`, configure `Rack::Cors` by passing a block to the `use` command:
|
61
55
|
|
@@ -68,16 +62,22 @@ use Rack::Cors do
|
|
68
62
|
|
69
63
|
resource '/file/list_all/', :headers => 'x-domain-token'
|
70
64
|
resource '/file/at/*',
|
71
|
-
:
|
72
|
-
:
|
73
|
-
:
|
74
|
-
:
|
65
|
+
methods: [:get, :post, :delete, :put, :patch, :options, :head],
|
66
|
+
headers: 'x-domain-token',
|
67
|
+
expose: ['Some-Custom-Response-Header'],
|
68
|
+
max_age: 600
|
75
69
|
# headers to expose
|
76
70
|
end
|
77
71
|
|
78
72
|
allow do
|
79
73
|
origins '*'
|
80
|
-
resource '/public/*', :
|
74
|
+
resource '/public/*', headers: :any, methods: :get
|
75
|
+
|
76
|
+
# Only allow a request for a specific host
|
77
|
+
resource '/api/v1/*',
|
78
|
+
headers: :any,
|
79
|
+
methods: :get,
|
80
|
+
if: proc { |env| env['HTTP_HOST'] == 'api.example.com' }
|
81
81
|
end
|
82
82
|
end
|
83
83
|
```
|
@@ -91,20 +91,19 @@ end
|
|
91
91
|
#### Origin
|
92
92
|
Origins can be specified as a string, a regular expression, or as '\*' to allow all origins.
|
93
93
|
|
94
|
-
**\*SECURITY NOTE:** Be careful when using regular expressions to not accidentally be too inclusive. For example, the expression `/https:\/\/example\.com/` will match the domain *example.com.randomdomainname.co.uk*. It is recommended that any regular expression be enclosed with start & end string anchors
|
94
|
+
**\*SECURITY NOTE:** Be careful when using regular expressions to not accidentally be too inclusive. For example, the expression `/https:\/\/example\.com/` will match the domain *example.com.randomdomainname.co.uk*. It is recommended that any regular expression be enclosed with start & end string anchors, like `\Ahttps:\/\/example\.com\z`.
|
95
95
|
|
96
96
|
Additionally, origins can be specified dynamically via a block of the following form:
|
97
97
|
```ruby
|
98
98
|
origins { |source, env| true || false }
|
99
99
|
```
|
100
100
|
|
101
|
-
#### Resource
|
102
101
|
A Resource path can be specified as exact string match (`/path/to/file.txt`) or with a '\*' wildcard (`/all/files/in/*`). A resource can take the following options:
|
103
102
|
|
104
103
|
* **methods** (string or array or `:any`): The HTTP methods allowed for the resource.
|
105
104
|
* **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.
|
106
105
|
* **expose** (string or array): The HTTP headers in the resource response can be exposed to the client.
|
107
|
-
* **credentials** (boolean): Sets the `Access-Control-Allow-Credentials` response header.
|
106
|
+
* **credentials** (boolean, default: `false`): Sets the `Access-Control-Allow-Credentials` response header. **Note:** If a wildcard (`*`) origin is specified, this option cannot be set to `true`. Read this [security article](http://web-in-security.blogspot.de/2017/07/cors-misconfigurations-on-large-scale.html) for more information.
|
108
107
|
* **max_age** (number): Sets the `Access-Control-Max-Age` response header.
|
109
108
|
* **if** (Proc): If the result of the proc is true, will process the request as a valid CORS request.
|
110
109
|
* **vary** (string or array): A list of HTTP headers to add to the 'Vary' header.
|
@@ -112,24 +111,50 @@ A Resource path can be specified as exact string match (`/path/to/file.txt`) or
|
|
112
111
|
|
113
112
|
## Common Gotchas
|
114
113
|
|
115
|
-
|
114
|
+
### Origin Matching
|
115
|
+
|
116
|
+
When specifying an origin, make sure that it does not have a trailing slash.
|
116
117
|
|
117
|
-
|
118
|
+
### Testing Postman and/or cURL
|
118
119
|
|
119
|
-
*
|
120
|
+
* Make sure you're passing in an `Origin:` header. That header is required to trigger a CORS response. Here's [a good SO post](https://stackoverflow.com/questions/12173990/how-can-you-debug-a-cors-request-with-curl) about using cURL for testing CORS.
|
121
|
+
* Make sure your origin does not have a trailing slash.
|
120
122
|
|
121
|
-
|
123
|
+
### Positioning in the Middleware Stack
|
122
124
|
|
123
|
-
|
125
|
+
Positioning of `Rack::Cors` in the middleware stack is very important. In the Rails example above we put it above all other middleware which, in our experience, provides the most consistent results.
|
124
126
|
|
125
|
-
|
127
|
+
Here are some scenarios where incorrect positioning have created issues:
|
128
|
+
|
129
|
+
* **Serving static files.** Insert before `ActionDispatch::Static` so that static files are served with the proper CORS headers. **NOTE:** this might not work in production as static files are usually served from the web server (Nginx, Apache) and not the Rails container.
|
130
|
+
|
131
|
+
* **Caching in the middleware.** Insert before `Rack::Cache` so that the proper CORS headers are written and not cached ones.
|
132
|
+
|
133
|
+
* **Authentication via Warden** Warden will return immediately if a resource that requires authentication is accessed without authentication. If `Warden::Manager`is in the stack before `Rack::Cors`, it will return without the correct CORS headers being applied, resulting in a failed CORS request.
|
134
|
+
|
135
|
+
You can run the following command to see what the middleware stack looks like:
|
126
136
|
|
127
137
|
```bash
|
128
138
|
bundle exec rake middleware
|
129
139
|
```
|
130
140
|
|
131
|
-
|
141
|
+
Note that the middleware stack is different in production. For example, the `ActionDispatch::Static` middleware will not be part of the stack if `config.serve_static_assets = false`. You can run this to see what your middleware stack looks like in production:
|
132
142
|
|
133
143
|
```bash
|
134
144
|
RAILS_ENV=production bundle exec rake middleware
|
135
145
|
```
|
146
|
+
|
147
|
+
### Serving static files
|
148
|
+
|
149
|
+
If you trying to serve CORS headers on static assets (like CSS, JS, Font files), keep in mind that static files are usually served directly from web servers and never runs through the Rails container (including the middleware stack where `Rack::Cors` resides).
|
150
|
+
|
151
|
+
In Heroku, you can serve static assets through the Rails container by setting `config.serve_static_assets = true` in `production.rb`.
|
152
|
+
|
153
|
+
### Custom Protocols (chrome-extension://, ionic://, etc.)
|
154
|
+
|
155
|
+
Prior to 2.0.0, `http://`, `https://`, and `file://` are the only protocols supported in the `origins` list. If you wish to specify an origin that
|
156
|
+
has a custom protocol (`chrome-extension://`, `ionic://`, etc.) simply exclude the protocol. [See issue.](https://github.com/cyu/rack-cors/issues/100)
|
157
|
+
|
158
|
+
For example, instead of specifying `chrome-extension://aomjjhallfgjeglblehebfpbcfeobpga` specify `aomjjhallfgjeglblehebfpbcfeobpga` in `origins`.
|
159
|
+
|
160
|
+
As of 2.0.0 (currently in RC1), you can specify origins with a custom protocol.
|
data/Rakefile
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
2
4
|
|
3
5
|
require 'rake/testtask'
|
4
6
|
Rake::TestTask.new(:test) do |test|
|
@@ -7,15 +9,14 @@ Rake::TestTask.new(:test) do |test|
|
|
7
9
|
test.verbose = true
|
8
10
|
end
|
9
11
|
|
10
|
-
task :
|
12
|
+
task default: :test
|
11
13
|
|
12
14
|
require 'rdoc/task'
|
13
15
|
Rake::RDocTask.new do |rdoc|
|
14
|
-
version = File.exist?('VERSION') ? File.read('VERSION') :
|
16
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ''
|
15
17
|
|
16
18
|
rdoc.rdoc_dir = 'rdoc'
|
17
19
|
rdoc.title = "rack-cors #{version}"
|
18
20
|
rdoc.rdoc_files.include('README*')
|
19
21
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
20
22
|
end
|
21
|
-
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class Cors
|
5
|
+
class Resource
|
6
|
+
# All CORS routes need to accept CORS simple headers at all times
|
7
|
+
# {https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers}
|
8
|
+
CORS_SIMPLE_HEADERS = %w[accept accept-language content-language content-type].freeze
|
9
|
+
|
10
|
+
attr_accessor :path, :methods, :headers, :expose, :max_age, :credentials, :pattern, :if_proc, :vary_headers
|
11
|
+
|
12
|
+
def initialize(public_resource, path, opts = {})
|
13
|
+
raise CorsMisconfigurationError if public_resource && opts[:credentials] == true
|
14
|
+
|
15
|
+
self.path = path
|
16
|
+
self.credentials = public_resource ? false : (opts[:credentials] == true)
|
17
|
+
self.max_age = opts[:max_age] || 7200
|
18
|
+
self.pattern = compile(path)
|
19
|
+
self.if_proc = opts[:if]
|
20
|
+
self.vary_headers = opts[:vary] && [opts[:vary]].flatten
|
21
|
+
@public_resource = public_resource
|
22
|
+
|
23
|
+
self.headers = case opts[:headers]
|
24
|
+
when :any then :any
|
25
|
+
when nil then nil
|
26
|
+
else
|
27
|
+
[opts[:headers]].flatten.collect(&:downcase)
|
28
|
+
end
|
29
|
+
|
30
|
+
self.methods = case opts[:methods]
|
31
|
+
when :any then %i[get head post put patch delete options]
|
32
|
+
else
|
33
|
+
ensure_enum(opts[:methods]) || [:get]
|
34
|
+
end.map(&:to_s)
|
35
|
+
|
36
|
+
self.expose = opts[:expose] ? [opts[:expose]].flatten : nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def matches_path?(path)
|
40
|
+
pattern =~ path
|
41
|
+
end
|
42
|
+
|
43
|
+
def match?(path, env)
|
44
|
+
matches_path?(path) && (if_proc.nil? || if_proc.call(env))
|
45
|
+
end
|
46
|
+
|
47
|
+
def process_preflight(env, result)
|
48
|
+
headers = {}
|
49
|
+
|
50
|
+
request_method = env[Rack::Cors::HTTP_ACCESS_CONTROL_REQUEST_METHOD]
|
51
|
+
result.miss(Result::MISS_NO_METHOD) && (return headers) if request_method.nil?
|
52
|
+
result.miss(Result::MISS_DENY_METHOD) && (return headers) unless methods.include?(request_method.downcase)
|
53
|
+
|
54
|
+
request_headers = env[Rack::Cors::HTTP_ACCESS_CONTROL_REQUEST_HEADERS]
|
55
|
+
result.miss(Result::MISS_DENY_HEADER) && (return headers) if request_headers && !allow_headers?(request_headers)
|
56
|
+
|
57
|
+
result.hit = true
|
58
|
+
headers.merge(to_preflight_headers(env))
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_headers(env)
|
62
|
+
h = {
|
63
|
+
'access-control-allow-origin' => origin_for_response_header(env[Rack::Cors::HTTP_ORIGIN]),
|
64
|
+
'access-control-allow-methods' => methods.collect { |m| m.to_s.upcase }.join(', '),
|
65
|
+
'access-control-expose-headers' => expose.nil? ? '' : expose.join(', '),
|
66
|
+
'access-control-max-age' => max_age.to_s
|
67
|
+
}
|
68
|
+
h['access-control-allow-credentials'] = 'true' if credentials
|
69
|
+
header_proc.call(h)
|
70
|
+
end
|
71
|
+
|
72
|
+
protected
|
73
|
+
|
74
|
+
def public_resource?
|
75
|
+
@public_resource
|
76
|
+
end
|
77
|
+
|
78
|
+
def origin_for_response_header(origin)
|
79
|
+
return '*' if public_resource?
|
80
|
+
|
81
|
+
origin
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_preflight_headers(env)
|
85
|
+
h = to_headers(env)
|
86
|
+
h.merge!('access-control-allow-headers' => env[Rack::Cors::HTTP_ACCESS_CONTROL_REQUEST_HEADERS]) if env[Rack::Cors::HTTP_ACCESS_CONTROL_REQUEST_HEADERS]
|
87
|
+
h
|
88
|
+
end
|
89
|
+
|
90
|
+
def allow_headers?(request_headers)
|
91
|
+
headers = self.headers || []
|
92
|
+
return true if headers == :any
|
93
|
+
|
94
|
+
request_headers = request_headers.split(/,\s*/) if request_headers.is_a?(String)
|
95
|
+
request_headers.all? do |header|
|
96
|
+
header = header.downcase
|
97
|
+
CORS_SIMPLE_HEADERS.include?(header) || headers.include?(header)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def ensure_enum(var)
|
102
|
+
return nil if var.nil?
|
103
|
+
|
104
|
+
[var].flatten
|
105
|
+
end
|
106
|
+
|
107
|
+
def compile(path)
|
108
|
+
if path.respond_to? :to_str
|
109
|
+
special_chars = %w[. + ( )]
|
110
|
+
pattern =
|
111
|
+
path.to_str.gsub(%r{((:\w+)|/\*|[\*#{special_chars.join}])}) do |match|
|
112
|
+
case match
|
113
|
+
when '/*'
|
114
|
+
'\\/?(.*?)'
|
115
|
+
when '*'
|
116
|
+
'(.*?)'
|
117
|
+
when *special_chars
|
118
|
+
Regexp.escape(match)
|
119
|
+
else
|
120
|
+
'([^/?&#]+)'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
/^#{pattern}$/
|
124
|
+
elsif path.respond_to? :match
|
125
|
+
path
|
126
|
+
else
|
127
|
+
raise TypeError, path
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def header_proc
|
132
|
+
@header_proc ||= begin
|
133
|
+
if defined?(Rack::Headers)
|
134
|
+
->(h) { h }
|
135
|
+
else
|
136
|
+
->(h) { Rack::Utils::HeaderHash.new(h) }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class Cors
|
5
|
+
class Resource
|
6
|
+
class CorsMisconfigurationError < StandardError
|
7
|
+
def message
|
8
|
+
'Allowing credentials for wildcard origins is insecure.' \
|
9
|
+
" Please specify more restrictive origins or set 'credentials' to false in your CORS configuration."
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'resources/cors_misconfiguration_error'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
class Cors
|
7
|
+
class Resources
|
8
|
+
attr_reader :resources
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@origins = []
|
12
|
+
@resources = []
|
13
|
+
@public_resources = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def origins(*args, &blk)
|
17
|
+
@origins = args.flatten.reject { |s| s == '' }.map do |n|
|
18
|
+
case n
|
19
|
+
when Proc, Regexp, %r{^[a-z][a-z0-9.+-]*://}
|
20
|
+
n
|
21
|
+
when '*'
|
22
|
+
@public_resources = true
|
23
|
+
n
|
24
|
+
else
|
25
|
+
Regexp.compile("^[a-z][a-z0-9.+-]*:\\\/\\\/#{Regexp.quote(n)}$")
|
26
|
+
end
|
27
|
+
end.flatten
|
28
|
+
@origins.push(blk) if blk
|
29
|
+
end
|
30
|
+
|
31
|
+
def resource(path, opts = {})
|
32
|
+
@resources << Resource.new(public_resources?, path, opts)
|
33
|
+
end
|
34
|
+
|
35
|
+
def public_resources?
|
36
|
+
@public_resources
|
37
|
+
end
|
38
|
+
|
39
|
+
def allow_origin?(source, env = {})
|
40
|
+
return true if public_resources?
|
41
|
+
|
42
|
+
!!@origins.detect do |origin|
|
43
|
+
if origin.is_a?(Proc)
|
44
|
+
origin.call(source, env)
|
45
|
+
elsif origin.is_a?(Regexp)
|
46
|
+
source =~ origin
|
47
|
+
else
|
48
|
+
source == origin
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def match_resource(path, env)
|
54
|
+
@resources.detect { |r| r.match?(path, env) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def resource_for_path(path)
|
58
|
+
@resources.detect { |r| r.matches_path?(path) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|