rack-cors 1.0.2 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 7a4d5e6683440676f486ce61b3c16ff2e7c8f65e
4
- data.tar.gz: be95c0c3dce56c965aff4b1cb398f2ad6fb4f6b3
2
+ SHA256:
3
+ metadata.gz: 14eb27e856c7cdd90b79cfc7236f3b71e10cfdcb2e282bb1a93546b8a97a053e
4
+ data.tar.gz: c9a2d87407a44c7df88129ece2d8ac5b8f5f6c46414b742c762d63cb99ae2278
5
5
  SHA512:
6
- metadata.gz: 8ae27dfd82bd822e700c476963118b15e68dee7850aaccb8b43a05670903f9f66217c8cd77dcf425f664581ecadf00eb43c1c1926648d97caf07c6d4a4dd0574
7
- data.tar.gz: df278b2f1b3e6f0b01305d601fb58f2d41aef0579232d5ae27564be76483afdfb58b9219708d5a944c7db293025d7bacec4924dc63c06aefed2a7df4ad00e9ec
6
+ metadata.gz: e7a4136f89a39be61d5d1c7cdc1d5d4c85a883ee72f9a0f16c13fdd0218918d9c67a379974bae60271f3a7b1e2aebdaf8c187aa65c9e97b4d12427a9c606af60
7
+ data.tar.gz: a6798527bc3b3463f93d63bb42e901ff11de4512c63e10baf2ccb413526fff0cbee04bd9c2c38025b840c5ce9281fd60cd473d4dd0631a1cb6ba704d7d4d28f0
@@ -1,6 +1,53 @@
1
1
  # Change Log
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
+ ## 2.0.2 - 2024-03-04
5
+ ### Changed
6
+ - Fix file permission issues with 2.0.1 release
7
+
8
+ ## 2.0.1 - 2023-02-17
9
+ ### Changed
10
+ - Use Rack::Utils::HeaderHash when Rack 2.x is detected
11
+
12
+ ## 2.0.0 - 2023-02-14
13
+ ### Changed
14
+ - Refactored codebase
15
+ - Support declaring custom protocols in origin
16
+ - Lowercased header names as defined by Rack spec
17
+ - Fix issue with duplicate headers because of header name case
18
+
19
+ ## 1.1.1 - 2019-12-29
20
+ ### Changed
21
+ - Allow /<resource>/* to match /<resource>/ and /<resource> paths
22
+
23
+ ## 1.1.0 - 2019-11-19
24
+ ### Changed
25
+ - Use Rack::Utils.escape_path instead of Rack::Utils.escape
26
+ - Require Rack 2.0 for escape_path method
27
+ - Don't try to clean path if invalid.
28
+ - Return 400 (Bad Request) on preflights with invalid path
29
+
30
+ ## 1.0.6 - 2019-11-14
31
+ ### Changed
32
+ - Use Rack::Utils.escape to make compat with Rack 1.6.0
33
+
34
+ ## 1.0.5 - 2019-11-14
35
+ ### Changed
36
+ - Update Gem spec to require rack >= 1.6.0
37
+
38
+ ## 1.0.4 - 2019-11-13
39
+ ### Security
40
+ - Escape and resolve path before evaluating resource rules (thanks to Colby Morgan)
41
+
42
+ ## 1.0.3 - 2019-03-24
43
+ ### Changed
44
+ - Don't send 'Content-Type' header with pre-flight requests
45
+ - Allow ruby array for vary header config
46
+
47
+ ## 1.0.2 - 2017-10-22
48
+ ### Fixed
49
+ - Automatically allow simple headers when headers are set
50
+
4
51
  ## 1.0.1 - 2017-07-18
5
52
  ### Fixed
6
53
  - Allow lambda origin configuration
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # Rack CORS Middleware [![Build Status](https://travis-ci.org/cyu/rack-cors.svg?branch=master)](https://travis-ci.org/cyu/rack-cors)
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 [Cross-domain Ajax with Cross-Origin Resource Sharing](http://www.nczonline.net/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/)
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,50 +13,37 @@ Install the gem:
13
13
  Or in your Gemfile:
14
14
 
15
15
  ```ruby
16
- gem 'rack-cors', :require => 'rack/cors'
16
+ gem 'rack-cors'
17
17
  ```
18
18
 
19
19
 
20
20
  ## Configuration
21
21
 
22
22
  ### Rails Configuration
23
- Put something like the code below in `config/application.rb` of your Rails application. For example, this will allow GET, POST or OPTIONS requests from any origin on any resource.
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
- module YourApp
27
- class Application < Rails::Application
28
-
29
- # ...
30
-
31
- # Rails 5
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 3/4
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
48
-
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]
49
32
  end
50
33
  end
51
34
  ```
52
35
 
53
- We use `insert_before` to make sure `Rack::Cors` runs at the beginning of the stack to make sure it isn't interfered with with other middleware (see `Rack::Cache` note in **Common Gotchas** section). Check out the [rails 4 example](https://github.com/cyu/rack-cors/tree/master/examples/rails4) and [rails 3 example](https://github.com/cyu/rack-cors/tree/master/examples/rails3).
36
+ NOTE: If you create application with `--api` option, configuration is automatically generated 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.
54
39
 
55
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).
56
41
 
42
+ Read more about it here in the [Rails Guides](https://guides.rubyonrails.org/configuring.html#configuring-middleware)
43
+
57
44
  ### Rack Configuration
58
45
 
59
- NOTE: If you're running Rails, updating in `config/application.rb` should be enough. There is no need to update `config.ru` as well.
46
+ NOTE: If you're running Rails, adding `config/initializers/cors.rb` should be enough. There is no need to update `config.ru` as well.
60
47
 
61
48
  In `config.ru`, configure `Rack::Cors` by passing a block to the `use` command:
62
49
 
@@ -69,16 +56,22 @@ use Rack::Cors do
69
56
 
70
57
  resource '/file/list_all/', :headers => 'x-domain-token'
71
58
  resource '/file/at/*',
72
- :methods => [:get, :post, :delete, :put, :patch, :options, :head],
73
- :headers => 'x-domain-token',
74
- :expose => ['Some-Custom-Response-Header'],
75
- :max_age => 600
59
+ methods: [:get, :post, :delete, :put, :patch, :options, :head],
60
+ headers: 'x-domain-token',
61
+ expose: ['Some-Custom-Response-Header'],
62
+ max_age: 600
76
63
  # headers to expose
77
64
  end
78
65
 
79
66
  allow do
80
67
  origins '*'
81
- resource '/public/*', :headers => :any, :methods => :get
68
+ resource '/public/*', headers: :any, methods: :get
69
+
70
+ # Only allow a request for a specific host
71
+ resource '/api/v1/*',
72
+ headers: :any,
73
+ methods: :get,
74
+ if: proc { |env| env['HTTP_HOST'] == 'api.example.com' }
82
75
  end
83
76
  end
84
77
  ```
@@ -92,14 +85,14 @@ end
92
85
  #### Origin
93
86
  Origins can be specified as a string, a regular expression, or as '\*' to allow all origins.
94
87
 
95
- **\*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 (`\A\z`).
88
+ **\*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`.
96
89
 
97
90
  Additionally, origins can be specified dynamically via a block of the following form:
98
91
  ```ruby
99
92
  origins { |source, env| true || false }
100
93
  ```
101
94
 
102
- A Resource path can be specified as exact string match (`/path/to/file.txt`) or with a '\*' wildcard (`/all/files/in/*`). To include all of a directory's files and the files in its subdirectories, use this form: `/assets/**/*`. A resource can take the following options:
95
+ 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
96
 
104
97
  * **methods** (string or array or `:any`): The HTTP methods allowed for the resource.
105
98
  * **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.
@@ -112,24 +105,54 @@ A Resource path can be specified as exact string match (`/path/to/file.txt`) or
112
105
 
113
106
  ## Common Gotchas
114
107
 
115
- Incorrect positioning of `Rack::Cors` in the middleware stack can produce unexpected results. The Rails example above will put it above all middleware which should cover most issues.
108
+ ### Origin Matching
109
+
110
+ When specifying an origin, make sure that it does not have a trailing slash.
111
+
112
+ ### Testing Postman and/or cURL
113
+
114
+ * 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.
115
+ * Make sure your origin does not have a trailing slash.
116
+
117
+ ### Positioning in the Middleware Stack
116
118
 
117
- Here are some common cases:
119
+ 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.
118
120
 
119
- * **Serving static files.** Insert this middleware before `ActionDispatch::Static` so that static files are served with the proper CORS headers (see note below for a caveat). **NOTE:** that this might not work in production environments as static files are usually served from the web server (Nginx, Apache) and not the Rails container.
121
+ Here are some scenarios where incorrect positioning have created issues:
120
122
 
121
- * **Caching in the middleware.** Insert this middleware before `Rack::Cache` so that the proper CORS headers are written and not cached ones.
123
+ * **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.
122
124
 
123
- * **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. Be sure to insert this middleware before 'Warden::Manager`.
125
+ * **Caching in the middleware.** Insert before `Rack::Cache` so that the proper CORS headers are written and not cached ones.
124
126
 
125
- To determine where to put the CORS middleware in the Rack stack, run the following command:
127
+ * **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.
128
+
129
+ You can run the following command to see what the middleware stack looks like:
126
130
 
127
131
  ```bash
128
132
  bundle exec rake middleware
129
133
  ```
130
134
 
131
- 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:
135
+ 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
136
 
133
137
  ```bash
134
138
  RAILS_ENV=production bundle exec rake middleware
135
139
  ```
140
+
141
+ ### Serving static files
142
+
143
+ 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).
144
+
145
+ In Heroku, you can serve static assets through the Rails container by setting `config.serve_static_assets = true` in `production.rb`.
146
+
147
+ ### Custom Protocols (chrome-extension://, ionic://, etc.)
148
+
149
+ 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
150
+ has a custom protocol (`chrome-extension://`, `ionic://`, etc.) simply exclude the protocol. [See issue.](https://github.com/cyu/rack-cors/issues/100)
151
+
152
+ For example, instead of specifying `chrome-extension://aomjjhallfgjeglblehebfpbcfeobpga` specify `aomjjhallfgjeglblehebfpbcfeobpga` in `origins`.
153
+
154
+ As of 2.0.0 (currently in RC1), you can specify origins with a custom protocol.
155
+
156
+ ### Rails 6 Host Matching
157
+
158
+ Rails 6 will block requests from unauthorized hosts, and this issue can be confused as a CORS related error. So in development, if you're making requests using something other than localhost or 127.0.0.1, make sure the server host has been authorized. [More info here](https://guides.rubyonrails.org/configuring.html#actiondispatch-hostauthorization)
@@ -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
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class Cors
3
- VERSION = "1.0.2"
5
+ VERSION = '2.0.2'
4
6
  end
5
7
  end