rack-cors 1.0.5 → 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 +4 -4
- data/.github/workflows/ci.yaml +39 -0
- data/.rubocop.yml +31 -0
- data/CHANGELOG.md +26 -0
- data/Gemfile +2 -0
- data/README.md +56 -35
- 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 +105 -346
- data/rack-cors.gemspec +20 -17
- data/test/.rubocop.yml +8 -0
- data/test/cors/test.cors.coffee +4 -2
- data/test/cors/test.cors.js +6 -2
- data/test/unit/cors_test.rb +174 -156
- data/test/unit/dsl_test.rb +30 -29
- data/test/unit/insecure.ru +2 -0
- data/test/unit/non_http.ru +2 -0
- data/test/unit/test.ru +24 -20
- metadata +52 -17
- data/.travis.yml +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
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
CHANGED
@@ -1,6 +1,32 @@
|
|
1
1
|
# Change Log
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
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
|
+
|
4
30
|
## 1.0.5 - 2019-11-14
|
5
31
|
### Changed
|
6
32
|
- Update Gem spec to require rack >= 1.6.0
|
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
|
|
@@ -20,41 +20,36 @@ gem 'rack-cors'
|
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
config.middleware.insert_before 0, Rack::Cors do
|
33
|
-
allow do
|
34
|
-
origins '*'
|
35
|
-
resource '*', headers: :any, methods: [:get, :post, :options]
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# Rails 3/4
|
40
|
-
|
41
|
-
config.middleware.insert_before 0, "Rack::Cors" do
|
42
|
-
allow do
|
43
|
-
origins '*'
|
44
|
-
resource '*', headers: :any, methods: [:get, :post, :options]
|
45
|
-
end
|
46
|
-
end
|
26
|
+
# config/initializers/cors.rb
|
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]
|
47
32
|
end
|
48
33
|
end
|
49
34
|
```
|
50
35
|
|
51
|
-
|
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.
|
52
39
|
|
53
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).
|
54
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
|
+
|
55
50
|
### Rack Configuration
|
56
51
|
|
57
|
-
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.
|
58
53
|
|
59
54
|
In `config.ru`, configure `Rack::Cors` by passing a block to the `use` command:
|
60
55
|
|
@@ -96,14 +91,14 @@ end
|
|
96
91
|
#### Origin
|
97
92
|
Origins can be specified as a string, a regular expression, or as '\*' to allow all origins.
|
98
93
|
|
99
|
-
**\*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`.
|
100
95
|
|
101
96
|
Additionally, origins can be specified dynamically via a block of the following form:
|
102
97
|
```ruby
|
103
98
|
origins { |source, env| true || false }
|
104
99
|
```
|
105
100
|
|
106
|
-
A Resource path can be specified as exact string match (`/path/to/file.txt`) or with a '\*' wildcard (`/all/files/in/*`).
|
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:
|
107
102
|
|
108
103
|
* **methods** (string or array or `:any`): The HTTP methods allowed for the resource.
|
109
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.
|
@@ -116,24 +111,50 @@ A Resource path can be specified as exact string match (`/path/to/file.txt`) or
|
|
116
111
|
|
117
112
|
## Common Gotchas
|
118
113
|
|
119
|
-
|
114
|
+
### Origin Matching
|
115
|
+
|
116
|
+
When specifying an origin, make sure that it does not have a trailing slash.
|
117
|
+
|
118
|
+
### Testing Postman and/or cURL
|
120
119
|
|
121
|
-
Here
|
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.
|
122
122
|
|
123
|
-
|
123
|
+
### Positioning in the Middleware Stack
|
124
124
|
|
125
|
-
|
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.
|
126
126
|
|
127
|
-
|
127
|
+
Here are some scenarios where incorrect positioning have created issues:
|
128
128
|
|
129
|
-
|
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:
|
130
136
|
|
131
137
|
```bash
|
132
138
|
bundle exec rake middleware
|
133
139
|
```
|
134
140
|
|
135
|
-
|
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:
|
136
142
|
|
137
143
|
```bash
|
138
144
|
RAILS_ENV=production bundle exec rake middleware
|
139
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
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class Cors
|
5
|
+
class Result
|
6
|
+
HEADER_KEY = 'x-rack-cors'
|
7
|
+
|
8
|
+
MISS_NO_ORIGIN = 'no-origin'
|
9
|
+
MISS_NO_PATH = 'no-path'
|
10
|
+
|
11
|
+
MISS_NO_METHOD = 'no-method'
|
12
|
+
MISS_DENY_METHOD = 'deny-method'
|
13
|
+
MISS_DENY_HEADER = 'deny-header'
|
14
|
+
|
15
|
+
attr_accessor :preflight, :hit, :miss_reason
|
16
|
+
|
17
|
+
def hit?
|
18
|
+
!!hit
|
19
|
+
end
|
20
|
+
|
21
|
+
def preflight?
|
22
|
+
!!preflight
|
23
|
+
end
|
24
|
+
|
25
|
+
def miss(reason)
|
26
|
+
self.hit = false
|
27
|
+
self.miss_reason = reason
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.hit(env)
|
31
|
+
r = Result.new
|
32
|
+
r.preflight = false
|
33
|
+
r.hit = true
|
34
|
+
env[Rack::Cors::ENV_KEY] = r
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.miss(env, reason)
|
38
|
+
r = Result.new
|
39
|
+
r.preflight = false
|
40
|
+
r.hit = false
|
41
|
+
r.miss_reason = reason
|
42
|
+
env[Rack::Cors::ENV_KEY] = r
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.preflight(env)
|
46
|
+
r = Result.new
|
47
|
+
r.preflight = true
|
48
|
+
env[Rack::Cors::ENV_KEY] = r
|
49
|
+
end
|
50
|
+
|
51
|
+
def append_header(headers)
|
52
|
+
headers[HEADER_KEY] = if hit?
|
53
|
+
preflight? ? 'preflight-hit' : 'hit'
|
54
|
+
else
|
55
|
+
[
|
56
|
+
(preflight? ? 'preflight-miss' : 'miss'),
|
57
|
+
miss_reason
|
58
|
+
].join('; ')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|