rack-cors 1.1.1 → 2.0.0.rc1
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +31 -0
- data/.travis.yml +6 -1
- data/CHANGELOG.md +6 -0
- data/Gemfile +2 -0
- data/README.md +43 -33
- data/Rakefile +5 -4
- data/lib/rack/cors/resource.rb +132 -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 +101 -354
- 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 +164 -158
- 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 -21
- metadata +49 -14
    
        data/lib/rack/cors.rb
    CHANGED
    
    | @@ -1,36 +1,38 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'logger'
         | 
| 4 | 
            +
            require_relative 'cors/resources'
         | 
| 5 | 
            +
            require_relative 'cors/resource'
         | 
| 6 | 
            +
            require_relative 'cors/result'
         | 
| 7 | 
            +
            require_relative 'cors/version'
         | 
| 2 8 |  | 
| 3 9 | 
             
            module Rack
         | 
| 4 10 | 
             
              class Cors
         | 
| 5 | 
            -
                HTTP_ORIGIN   = 'HTTP_ORIGIN' | 
| 6 | 
            -
                HTTP_X_ORIGIN = 'HTTP_X_ORIGIN' | 
| 11 | 
            +
                HTTP_ORIGIN   = 'HTTP_ORIGIN'
         | 
| 12 | 
            +
                HTTP_X_ORIGIN = 'HTTP_X_ORIGIN'
         | 
| 7 13 |  | 
| 8 | 
            -
                HTTP_ACCESS_CONTROL_REQUEST_METHOD  = 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' | 
| 9 | 
            -
                HTTP_ACCESS_CONTROL_REQUEST_HEADERS = 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS' | 
| 14 | 
            +
                HTTP_ACCESS_CONTROL_REQUEST_METHOD  = 'HTTP_ACCESS_CONTROL_REQUEST_METHOD'
         | 
| 15 | 
            +
                HTTP_ACCESS_CONTROL_REQUEST_HEADERS = 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS'
         | 
| 10 16 |  | 
| 11 | 
            -
                PATH_INFO      = 'PATH_INFO' | 
| 12 | 
            -
                REQUEST_METHOD = 'REQUEST_METHOD' | 
| 17 | 
            +
                PATH_INFO      = 'PATH_INFO'
         | 
| 18 | 
            +
                REQUEST_METHOD = 'REQUEST_METHOD'
         | 
| 13 19 |  | 
| 14 | 
            -
                RACK_LOGGER = 'rack.logger' | 
| 20 | 
            +
                RACK_LOGGER = 'rack.logger'
         | 
| 15 21 | 
             
                RACK_CORS   =
         | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 22 | 
            +
                  # retaining the old key for backwards compatibility
         | 
| 23 | 
            +
                  ENV_KEY = 'rack.cors'
         | 
| 18 24 |  | 
| 19 | 
            -
                OPTIONS     = 'OPTIONS' | 
| 20 | 
            -
                VARY        = 'Vary'.freeze
         | 
| 25 | 
            +
                OPTIONS     = 'OPTIONS'
         | 
| 21 26 |  | 
| 22 27 | 
             
                DEFAULT_VARY_HEADERS = ['Origin'].freeze
         | 
| 23 28 |  | 
| 24 | 
            -
                 | 
| 25 | 
            -
                # {https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers}
         | 
| 26 | 
            -
                CORS_SIMPLE_HEADERS = ['accept', 'accept-language', 'content-language', 'content-type'].freeze
         | 
| 27 | 
            -
             | 
| 28 | 
            -
                def initialize(app, opts={}, &block)
         | 
| 29 | 
            +
                def initialize(app, opts = {}, &block)
         | 
| 29 30 | 
             
                  @app = app
         | 
| 30 31 | 
             
                  @debug_mode = !!opts[:debug]
         | 
| 31 32 | 
             
                  @logger = @logger_proc = nil
         | 
| 32 33 |  | 
| 33 | 
            -
                   | 
| 34 | 
            +
                  logger = opts[:logger]
         | 
| 35 | 
            +
                  if logger
         | 
| 34 36 | 
             
                    if logger.respond_to? :call
         | 
| 35 37 | 
             
                      @logger_proc = opts[:logger]
         | 
| 36 38 | 
             
                    else
         | 
| @@ -38,12 +40,12 @@ module Rack | |
| 38 40 | 
             
                    end
         | 
| 39 41 | 
             
                  end
         | 
| 40 42 |  | 
| 41 | 
            -
                   | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
                     | 
| 45 | 
            -
             | 
| 46 | 
            -
                     | 
| 43 | 
            +
                  return unless block_given?
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  if block.arity == 1
         | 
| 46 | 
            +
                    block.call(self)
         | 
| 47 | 
            +
                  else
         | 
| 48 | 
            +
                    instance_eval(&block)
         | 
| 47 49 | 
             
                  end
         | 
| 48 50 | 
             
                end
         | 
| 49 51 |  | 
| @@ -69,20 +71,20 @@ module Rack | |
| 69 71 | 
             
                  add_headers = nil
         | 
| 70 72 | 
             
                  if env[HTTP_ORIGIN]
         | 
| 71 73 | 
             
                    debug(env) do
         | 
| 72 | 
            -
                      [ | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
                        ].join("\n")
         | 
| 74 | 
            +
                      ['Incoming Headers:',
         | 
| 75 | 
            +
                       "  Origin: #{env[HTTP_ORIGIN]}",
         | 
| 76 | 
            +
                       "  Path-Info: #{path}",
         | 
| 77 | 
            +
                       "  Access-Control-Request-Method: #{env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]}",
         | 
| 78 | 
            +
                       "  Access-Control-Request-Headers: #{env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]}"].join("\n")
         | 
| 78 79 | 
             
                    end
         | 
| 79 80 |  | 
| 80 81 | 
             
                    if env[REQUEST_METHOD] == OPTIONS && env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]
         | 
| 81 82 | 
             
                      return [400, {}, []] unless Rack::Utils.valid_path?(path)
         | 
| 83 | 
            +
             | 
| 82 84 | 
             
                      headers = process_preflight(env, path)
         | 
| 83 85 | 
             
                      debug(env) do
         | 
| 84 86 | 
             
                        "Preflight Headers:\n" +
         | 
| 85 | 
            -
             | 
| 87 | 
            +
                          headers.collect { |kv| "  #{kv.join(': ')}" }.join("\n")
         | 
| 86 88 | 
             
                      end
         | 
| 87 89 | 
             
                      return [200, headers, []]
         | 
| 88 90 | 
             
                    else
         | 
| @@ -103,9 +105,7 @@ module Rack | |
| 103 105 | 
             
                    headers = add_headers.merge(headers)
         | 
| 104 106 | 
             
                    debug(env) do
         | 
| 105 107 | 
             
                      add_headers.each_pair do |key, value|
         | 
| 106 | 
            -
                        if headers. | 
| 107 | 
            -
                          headers["X-Rack-CORS-Original-#{key}"] = value
         | 
| 108 | 
            -
                        end
         | 
| 108 | 
            +
                        headers["x-rack-cors-original-#{key}"] = value if headers.key?(key)
         | 
| 109 109 | 
             
                      end
         | 
| 110 110 | 
             
                    end
         | 
| 111 111 | 
             
                  end
         | 
| @@ -114,359 +114,106 @@ module Rack | |
| 114 114 | 
             
                  # response to be different depending on the Origin header value.
         | 
| 115 115 | 
             
                  # Better explained here: http://www.fastly.com/blog/best-practices-for-using-the-vary-header/
         | 
| 116 116 | 
             
                  if vary_resource
         | 
| 117 | 
            -
                    vary = headers[ | 
| 118 | 
            -
                    cors_vary_headers = if vary_resource.vary_headers | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 122 | 
            -
             | 
| 123 | 
            -
                    headers[ | 
| 117 | 
            +
                    vary = headers['vary']
         | 
| 118 | 
            +
                    cors_vary_headers = if vary_resource.vary_headers&.any?
         | 
| 119 | 
            +
                                          vary_resource.vary_headers
         | 
| 120 | 
            +
                                        else
         | 
| 121 | 
            +
                                          DEFAULT_VARY_HEADERS
         | 
| 122 | 
            +
                                        end
         | 
| 123 | 
            +
                    headers['vary'] = ((vary ? [vary].flatten.map { |v| v.split(/,\s*/) }.flatten : []) + cors_vary_headers).uniq.join(', ')
         | 
| 124 124 | 
             
                  end
         | 
| 125 125 |  | 
| 126 | 
            -
                   | 
| 127 | 
            -
             | 
| 128 | 
            -
                  end
         | 
| 126 | 
            +
                  result = env[ENV_KEY]
         | 
| 127 | 
            +
                  result.append_header(headers) if debug? && result
         | 
| 129 128 |  | 
| 130 129 | 
             
                  [status, headers, body]
         | 
| 131 130 | 
             
                end
         | 
| 132 131 |  | 
| 133 132 | 
             
                protected
         | 
| 134 | 
            -
                  def debug(env, message = nil, &block)
         | 
| 135 | 
            -
                    (@logger || select_logger(env)).debug(message, &block) if debug?
         | 
| 136 | 
            -
                  end
         | 
| 137 133 |  | 
| 138 | 
            -
             | 
| 139 | 
            -
             | 
| 140 | 
            -
             | 
| 141 | 
            -
                      @logger_proc = nil
         | 
| 142 | 
            -
                      logger_proc.call
         | 
| 134 | 
            +
                def debug(env, message = nil, &block)
         | 
| 135 | 
            +
                  (@logger || select_logger(env)).debug(message, &block) if debug?
         | 
| 136 | 
            +
                end
         | 
| 143 137 |  | 
| 144 | 
            -
             | 
| 145 | 
            -
             | 
| 138 | 
            +
                def select_logger(env)
         | 
| 139 | 
            +
                  @logger = if @logger_proc
         | 
| 140 | 
            +
                              logger_proc = @logger_proc
         | 
| 141 | 
            +
                              @logger_proc = nil
         | 
| 142 | 
            +
                              logger_proc.call
         | 
| 146 143 |  | 
| 147 | 
            -
             | 
| 148 | 
            -
             | 
| 144 | 
            +
                            elsif defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
         | 
| 145 | 
            +
                              Rails.logger
         | 
| 149 146 |  | 
| 150 | 
            -
             | 
| 151 | 
            -
             | 
| 152 | 
            -
                    end
         | 
| 153 | 
            -
                  end
         | 
| 147 | 
            +
                            elsif env[RACK_LOGGER]
         | 
| 148 | 
            +
                              env[RACK_LOGGER]
         | 
| 154 149 |  | 
| 155 | 
            -
             | 
| 156 | 
            -
             | 
| 150 | 
            +
                            else
         | 
| 151 | 
            +
                              ::Logger.new(STDOUT).tap { |logger| logger.level = ::Logger::Severity::DEBUG }
         | 
| 152 | 
            +
                            end
         | 
| 153 | 
            +
                end
         | 
| 157 154 |  | 
| 158 | 
            -
             | 
| 159 | 
            -
             | 
| 155 | 
            +
                def evaluate_path(env)
         | 
| 156 | 
            +
                  path = env[PATH_INFO]
         | 
| 160 157 |  | 
| 161 | 
            -
             | 
| 162 | 
            -
             | 
| 163 | 
            -
                      end
         | 
| 164 | 
            -
                    end
         | 
| 158 | 
            +
                  if path
         | 
| 159 | 
            +
                    path = Rack::Utils.unescape_path(path)
         | 
| 165 160 |  | 
| 166 | 
            -
                    path
         | 
| 161 | 
            +
                    path = Rack::Utils.clean_path_info(path) if Rack::Utils.valid_path?(path)
         | 
| 167 162 | 
             
                  end
         | 
| 168 163 |  | 
| 169 | 
            -
                   | 
| 170 | 
            -
             | 
| 171 | 
            -
                  end
         | 
| 164 | 
            +
                  path
         | 
| 165 | 
            +
                end
         | 
| 172 166 |  | 
| 173 | 
            -
             | 
| 174 | 
            -
             | 
| 167 | 
            +
                def all_resources
         | 
| 168 | 
            +
                  @all_resources ||= []
         | 
| 169 | 
            +
                end
         | 
| 175 170 |  | 
| 176 | 
            -
             | 
| 177 | 
            -
             | 
| 178 | 
            -
                      result.miss(error)
         | 
| 179 | 
            -
                      return {}
         | 
| 180 | 
            -
                    end
         | 
| 171 | 
            +
                def process_preflight(env, path)
         | 
| 172 | 
            +
                  result = Result.preflight(env)
         | 
| 181 173 |  | 
| 182 | 
            -
             | 
| 174 | 
            +
                  resource, error = match_resource(path, env)
         | 
| 175 | 
            +
                  unless resource
         | 
| 176 | 
            +
                    result.miss(error)
         | 
| 177 | 
            +
                    return {}
         | 
| 183 178 | 
             
                  end
         | 
| 184 179 |  | 
| 185 | 
            -
                   | 
| 186 | 
            -
             | 
| 187 | 
            -
                    if resource
         | 
| 188 | 
            -
                      Result.hit(env)
         | 
| 189 | 
            -
                      cors = resource.to_headers(env)
         | 
| 190 | 
            -
                      cors
         | 
| 180 | 
            +
                  resource.process_preflight(env, result)
         | 
| 181 | 
            +
                end
         | 
| 191 182 |  | 
| 192 | 
            -
             | 
| 193 | 
            -
             | 
| 194 | 
            -
             | 
| 195 | 
            -
                     | 
| 196 | 
            -
             | 
| 183 | 
            +
                def process_cors(env, path)
         | 
| 184 | 
            +
                  resource, error = match_resource(path, env)
         | 
| 185 | 
            +
                  if resource
         | 
| 186 | 
            +
                    Result.hit(env)
         | 
| 187 | 
            +
                    cors = resource.to_headers(env)
         | 
| 188 | 
            +
                    cors
         | 
| 197 189 |  | 
| 198 | 
            -
                   | 
| 199 | 
            -
                     | 
| 200 | 
            -
                      if found = r.resource_for_path(path_info)
         | 
| 201 | 
            -
                        return found
         | 
| 202 | 
            -
                      end
         | 
| 203 | 
            -
                    end
         | 
| 190 | 
            +
                  else
         | 
| 191 | 
            +
                    Result.miss(env, error)
         | 
| 204 192 | 
             
                    nil
         | 
| 205 193 | 
             
                  end
         | 
| 194 | 
            +
                end
         | 
| 206 195 |  | 
| 207 | 
            -
             | 
| 208 | 
            -
             | 
| 209 | 
            -
             | 
| 210 | 
            -
                     | 
| 211 | 
            -
                    all_resources.each do |r|
         | 
| 212 | 
            -
                      if r.allow_origin?(origin, env)
         | 
| 213 | 
            -
                        origin_matched = true
         | 
| 214 | 
            -
                        if found = r.match_resource(path, env)
         | 
| 215 | 
            -
                          return [found, nil]
         | 
| 216 | 
            -
                        end
         | 
| 217 | 
            -
                      end
         | 
| 218 | 
            -
                    end
         | 
| 219 | 
            -
             | 
| 220 | 
            -
                    [nil, origin_matched ? Result::MISS_NO_PATH : Result::MISS_NO_ORIGIN]
         | 
| 221 | 
            -
                  end
         | 
| 222 | 
            -
             | 
| 223 | 
            -
                  class Result
         | 
| 224 | 
            -
                    HEADER_KEY = 'X-Rack-CORS'.freeze
         | 
| 225 | 
            -
             | 
| 226 | 
            -
                    MISS_NO_ORIGIN = 'no-origin'.freeze
         | 
| 227 | 
            -
                    MISS_NO_PATH   = 'no-path'.freeze
         | 
| 228 | 
            -
             | 
| 229 | 
            -
                    MISS_NO_METHOD   = 'no-method'.freeze
         | 
| 230 | 
            -
                    MISS_DENY_METHOD = 'deny-method'.freeze
         | 
| 231 | 
            -
                    MISS_DENY_HEADER = 'deny-header'.freeze
         | 
| 232 | 
            -
             | 
| 233 | 
            -
                    attr_accessor :preflight, :hit, :miss_reason
         | 
| 234 | 
            -
             | 
| 235 | 
            -
                    def hit?
         | 
| 236 | 
            -
                      !!hit
         | 
| 237 | 
            -
                    end
         | 
| 238 | 
            -
             | 
| 239 | 
            -
                    def preflight?
         | 
| 240 | 
            -
                      !!preflight
         | 
| 241 | 
            -
                    end
         | 
| 242 | 
            -
             | 
| 243 | 
            -
                    def miss(reason)
         | 
| 244 | 
            -
                      self.hit = false
         | 
| 245 | 
            -
                      self.miss_reason = reason
         | 
| 246 | 
            -
                    end
         | 
| 247 | 
            -
             | 
| 248 | 
            -
                    def self.hit(env)
         | 
| 249 | 
            -
                      r = Result.new
         | 
| 250 | 
            -
                      r.preflight = false
         | 
| 251 | 
            -
                      r.hit = true
         | 
| 252 | 
            -
                      env[RACK_CORS] = r
         | 
| 253 | 
            -
                    end
         | 
| 254 | 
            -
             | 
| 255 | 
            -
                    def self.miss(env, reason)
         | 
| 256 | 
            -
                      r = Result.new
         | 
| 257 | 
            -
                      r.preflight = false
         | 
| 258 | 
            -
                      r.hit = false
         | 
| 259 | 
            -
                      r.miss_reason = reason
         | 
| 260 | 
            -
                      env[RACK_CORS] = r
         | 
| 261 | 
            -
                    end
         | 
| 262 | 
            -
             | 
| 263 | 
            -
                    def self.preflight(env)
         | 
| 264 | 
            -
                      r = Result.new
         | 
| 265 | 
            -
                      r.preflight = true
         | 
| 266 | 
            -
                      env[RACK_CORS] = r
         | 
| 267 | 
            -
                    end
         | 
| 268 | 
            -
             | 
| 269 | 
            -
             | 
| 270 | 
            -
                    def append_header(headers)
         | 
| 271 | 
            -
                      headers[HEADER_KEY] = if hit?
         | 
| 272 | 
            -
                        preflight? ? 'preflight-hit' : 'hit'
         | 
| 273 | 
            -
                      else
         | 
| 274 | 
            -
                        [
         | 
| 275 | 
            -
                          (preflight? ? 'preflight-miss' : 'miss'),
         | 
| 276 | 
            -
                          miss_reason
         | 
| 277 | 
            -
                        ].join('; ')
         | 
| 278 | 
            -
                      end
         | 
| 279 | 
            -
                    end
         | 
| 280 | 
            -
                  end
         | 
| 281 | 
            -
             | 
| 282 | 
            -
                  class Resources
         | 
| 283 | 
            -
             | 
| 284 | 
            -
                    attr_reader :resources
         | 
| 285 | 
            -
             | 
| 286 | 
            -
                    def initialize
         | 
| 287 | 
            -
                      @origins = []
         | 
| 288 | 
            -
                      @resources = []
         | 
| 289 | 
            -
                      @public_resources = false
         | 
| 290 | 
            -
                    end
         | 
| 291 | 
            -
             | 
| 292 | 
            -
                    def origins(*args, &blk)
         | 
| 293 | 
            -
                      @origins = args.flatten.reject{ |s| s == '' }.map do |n|
         | 
| 294 | 
            -
                        case n
         | 
| 295 | 
            -
                        when Proc,
         | 
| 296 | 
            -
                             Regexp,
         | 
| 297 | 
            -
                             /^https?:\/\//,
         | 
| 298 | 
            -
                             'file://'        then n
         | 
| 299 | 
            -
                        when '*'              then @public_resources = true; n
         | 
| 300 | 
            -
                        else                  Regexp.compile("^[a-z][a-z0-9.+-]*:\\\/\\\/#{Regexp.quote(n)}$")
         | 
| 301 | 
            -
                        end
         | 
| 302 | 
            -
                      end.flatten
         | 
| 303 | 
            -
                      @origins.push(blk) if blk
         | 
| 304 | 
            -
                    end
         | 
| 305 | 
            -
             | 
| 306 | 
            -
                    def resource(path, opts={})
         | 
| 307 | 
            -
                      @resources << Resource.new(public_resources?, path, opts)
         | 
| 308 | 
            -
                    end
         | 
| 309 | 
            -
             | 
| 310 | 
            -
                    def public_resources?
         | 
| 311 | 
            -
                      @public_resources
         | 
| 312 | 
            -
                    end
         | 
| 313 | 
            -
             | 
| 314 | 
            -
                    def allow_origin?(source,env = {})
         | 
| 315 | 
            -
                      return true if public_resources?
         | 
| 316 | 
            -
             | 
| 317 | 
            -
                      return !! @origins.detect do |origin|
         | 
| 318 | 
            -
                        if origin.is_a?(Proc)
         | 
| 319 | 
            -
                          origin.call(source,env)
         | 
| 320 | 
            -
                        else
         | 
| 321 | 
            -
                          origin === source
         | 
| 322 | 
            -
                        end
         | 
| 323 | 
            -
                      end
         | 
| 324 | 
            -
                    end
         | 
| 325 | 
            -
             | 
| 326 | 
            -
                    def match_resource(path, env)
         | 
| 327 | 
            -
                      @resources.detect { |r| r.match?(path, env) }
         | 
| 328 | 
            -
                    end
         | 
| 329 | 
            -
             | 
| 330 | 
            -
                    def resource_for_path(path)
         | 
| 331 | 
            -
                      @resources.detect { |r| r.matches_path?(path) }
         | 
| 332 | 
            -
                    end
         | 
| 333 | 
            -
             | 
| 196 | 
            +
                def resource_for_path(path_info)
         | 
| 197 | 
            +
                  all_resources.each do |r|
         | 
| 198 | 
            +
                    found = r.resource_for_path(path_info)
         | 
| 199 | 
            +
                    return found if found
         | 
| 334 200 | 
             
                  end
         | 
| 201 | 
            +
                  nil
         | 
| 202 | 
            +
                end
         | 
| 335 203 |  | 
| 336 | 
            -
             | 
| 337 | 
            -
             | 
| 338 | 
            -
                      def message
         | 
| 339 | 
            -
                        "Allowing credentials for wildcard origins is insecure."\
         | 
| 340 | 
            -
                        " Please specify more restrictive origins or set 'credentials' to false in your CORS configuration."
         | 
| 341 | 
            -
                      end
         | 
| 342 | 
            -
                    end
         | 
| 343 | 
            -
             | 
| 344 | 
            -
                    attr_accessor :path, :methods, :headers, :expose, :max_age, :credentials, :pattern, :if_proc, :vary_headers
         | 
| 345 | 
            -
             | 
| 346 | 
            -
                    def initialize(public_resource, path, opts={})
         | 
| 347 | 
            -
                      raise CorsMisconfigurationError if public_resource && opts[:credentials] == true
         | 
| 348 | 
            -
             | 
| 349 | 
            -
                      self.path         = path
         | 
| 350 | 
            -
                      self.credentials  = public_resource ? false : (opts[:credentials] == true)
         | 
| 351 | 
            -
                      self.max_age      = opts[:max_age] || 7200
         | 
| 352 | 
            -
                      self.pattern      = compile(path)
         | 
| 353 | 
            -
                      self.if_proc      = opts[:if]
         | 
| 354 | 
            -
                      self.vary_headers = opts[:vary] && [opts[:vary]].flatten
         | 
| 355 | 
            -
                      @public_resource  = public_resource
         | 
| 356 | 
            -
             | 
| 357 | 
            -
                      self.headers = case opts[:headers]
         | 
| 358 | 
            -
                      when :any then :any
         | 
| 359 | 
            -
                      when nil then nil
         | 
| 360 | 
            -
                      else
         | 
| 361 | 
            -
                        [opts[:headers]].flatten.collect{|h| h.downcase}
         | 
| 362 | 
            -
                      end
         | 
| 363 | 
            -
             | 
| 364 | 
            -
                      self.methods = case opts[:methods]
         | 
| 365 | 
            -
                      when :any then [:get, :head, :post, :put, :patch, :delete, :options]
         | 
| 366 | 
            -
                      else
         | 
| 367 | 
            -
                        ensure_enum(opts[:methods]) || [:get]
         | 
| 368 | 
            -
                      end.map{|e| e.to_s }
         | 
| 369 | 
            -
             | 
| 370 | 
            -
                      self.expose = opts[:expose] ? [opts[:expose]].flatten : nil
         | 
| 371 | 
            -
                    end
         | 
| 372 | 
            -
             | 
| 373 | 
            -
                    def matches_path?(path)
         | 
| 374 | 
            -
                      pattern =~ path
         | 
| 375 | 
            -
                    end
         | 
| 376 | 
            -
             | 
| 377 | 
            -
                    def match?(path, env)
         | 
| 378 | 
            -
                      matches_path?(path) && (if_proc.nil? || if_proc.call(env))
         | 
| 379 | 
            -
                    end
         | 
| 380 | 
            -
             | 
| 381 | 
            -
                    def process_preflight(env, result)
         | 
| 382 | 
            -
                      headers = {}
         | 
| 383 | 
            -
             | 
| 384 | 
            -
                      request_method = env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]
         | 
| 385 | 
            -
                      if request_method.nil?
         | 
| 386 | 
            -
                        result.miss(Result::MISS_NO_METHOD) and return headers
         | 
| 387 | 
            -
                      end
         | 
| 388 | 
            -
                      if !methods.include?(request_method.downcase)
         | 
| 389 | 
            -
                        result.miss(Result::MISS_DENY_METHOD) and return headers
         | 
| 390 | 
            -
                      end
         | 
| 391 | 
            -
             | 
| 392 | 
            -
                      request_headers = env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]
         | 
| 393 | 
            -
                      if request_headers && !allow_headers?(request_headers)
         | 
| 394 | 
            -
                        result.miss(Result::MISS_DENY_HEADER) and return headers
         | 
| 395 | 
            -
                      end
         | 
| 396 | 
            -
             | 
| 397 | 
            -
                      result.hit = true
         | 
| 398 | 
            -
                      headers.merge(to_preflight_headers(env))
         | 
| 399 | 
            -
                    end
         | 
| 400 | 
            -
             | 
| 401 | 
            -
                    def to_headers(env)
         | 
| 402 | 
            -
                      h = {
         | 
| 403 | 
            -
                        'Access-Control-Allow-Origin'     => origin_for_response_header(env[HTTP_ORIGIN]),
         | 
| 404 | 
            -
                        'Access-Control-Allow-Methods'    => methods.collect{|m| m.to_s.upcase}.join(', '),
         | 
| 405 | 
            -
                        'Access-Control-Expose-Headers'   => expose.nil? ? '' : expose.join(', '),
         | 
| 406 | 
            -
                        'Access-Control-Max-Age'          => max_age.to_s }
         | 
| 407 | 
            -
                      h['Access-Control-Allow-Credentials'] = 'true' if credentials
         | 
| 408 | 
            -
                      h
         | 
| 409 | 
            -
                    end
         | 
| 410 | 
            -
             | 
| 411 | 
            -
                    protected
         | 
| 412 | 
            -
                      def public_resource?
         | 
| 413 | 
            -
                        @public_resource
         | 
| 414 | 
            -
                      end
         | 
| 415 | 
            -
             | 
| 416 | 
            -
                      def origin_for_response_header(origin)
         | 
| 417 | 
            -
                        return '*' if public_resource?
         | 
| 418 | 
            -
                        origin
         | 
| 419 | 
            -
                      end
         | 
| 420 | 
            -
             | 
| 421 | 
            -
                      def to_preflight_headers(env)
         | 
| 422 | 
            -
                        h = to_headers(env)
         | 
| 423 | 
            -
                        if env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]
         | 
| 424 | 
            -
                          h.merge!('Access-Control-Allow-Headers' => env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS])
         | 
| 425 | 
            -
                        end
         | 
| 426 | 
            -
                        h
         | 
| 427 | 
            -
                      end
         | 
| 428 | 
            -
             | 
| 429 | 
            -
                      def allow_headers?(request_headers)
         | 
| 430 | 
            -
                        headers = self.headers || []
         | 
| 431 | 
            -
                        if headers == :any
         | 
| 432 | 
            -
                          return true
         | 
| 433 | 
            -
                        end
         | 
| 434 | 
            -
                        request_headers = request_headers.split(/,\s*/) if request_headers.kind_of?(String)
         | 
| 435 | 
            -
                        request_headers.all? do |header|
         | 
| 436 | 
            -
                          header = header.downcase
         | 
| 437 | 
            -
                          CORS_SIMPLE_HEADERS.include?(header) || headers.include?(header)
         | 
| 438 | 
            -
                        end
         | 
| 439 | 
            -
                      end
         | 
| 204 | 
            +
                def match_resource(path, env)
         | 
| 205 | 
            +
                  origin = env[HTTP_ORIGIN]
         | 
| 440 206 |  | 
| 441 | 
            -
             | 
| 442 | 
            -
             | 
| 443 | 
            -
             | 
| 444 | 
            -
                      end
         | 
| 207 | 
            +
                  origin_matched = false
         | 
| 208 | 
            +
                  all_resources.each do |r|
         | 
| 209 | 
            +
                    next unless r.allow_origin?(origin, env)
         | 
| 445 210 |  | 
| 446 | 
            -
             | 
| 447 | 
            -
             | 
| 448 | 
            -
             | 
| 449 | 
            -
                          pattern =
         | 
| 450 | 
            -
                            path.to_str.gsub(/((:\w+)|\/\*|[\*#{special_chars.join}])/) do |match|
         | 
| 451 | 
            -
                              case match
         | 
| 452 | 
            -
                              when "/*"
         | 
| 453 | 
            -
                                "\\/?(.*?)"
         | 
| 454 | 
            -
                              when "*"
         | 
| 455 | 
            -
                                "(.*?)"
         | 
| 456 | 
            -
                              when *special_chars
         | 
| 457 | 
            -
                                Regexp.escape(match)
         | 
| 458 | 
            -
                              else
         | 
| 459 | 
            -
                                "([^/?&#]+)"
         | 
| 460 | 
            -
                              end
         | 
| 461 | 
            -
                            end
         | 
| 462 | 
            -
                          /^#{pattern}$/
         | 
| 463 | 
            -
                        elsif path.respond_to? :match
         | 
| 464 | 
            -
                          path
         | 
| 465 | 
            -
                        else
         | 
| 466 | 
            -
                          raise TypeError, path
         | 
| 467 | 
            -
                        end
         | 
| 468 | 
            -
                      end
         | 
| 211 | 
            +
                    origin_matched = true
         | 
| 212 | 
            +
                    found = r.match_resource(path, env)
         | 
| 213 | 
            +
                    return [found, nil] if found
         | 
| 469 214 | 
             
                  end
         | 
| 470 215 |  | 
| 216 | 
            +
                  [nil, origin_matched ? Result::MISS_NO_PATH : Result::MISS_NO_ORIGIN]
         | 
| 217 | 
            +
                end
         | 
| 471 218 | 
             
              end
         | 
| 472 219 | 
             
            end
         | 
    
        data/rack-cors.gemspec
    CHANGED
    
    | @@ -1,27 +1,30 @@ | |
| 1 | 
            -
            #  | 
| 2 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            lib = File.expand_path('lib', __dir__)
         | 
| 3 4 | 
             
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 4 5 | 
             
            require 'rack/cors/version'
         | 
| 5 6 |  | 
| 6 7 | 
             
            Gem::Specification.new do |spec|
         | 
| 7 | 
            -
              spec.name          =  | 
| 8 | 
            +
              spec.name          = 'rack-cors'
         | 
| 8 9 | 
             
              spec.version       = Rack::Cors::VERSION
         | 
| 9 | 
            -
              spec.authors       = [ | 
| 10 | 
            -
              spec.email         = [ | 
| 11 | 
            -
              spec.description   =  | 
| 12 | 
            -
              spec.summary       =  | 
| 13 | 
            -
              spec.homepage      =  | 
| 14 | 
            -
              spec.license       =  | 
| 10 | 
            +
              spec.authors       = ['Calvin Yu']
         | 
| 11 | 
            +
              spec.email         = ['me@sourcebender.com']
         | 
| 12 | 
            +
              spec.description   = 'Middleware that will make Rack-based apps CORS compatible. Fork the project here: https://github.com/cyu/rack-cors'
         | 
| 13 | 
            +
              spec.summary       = 'Middleware for enabling Cross-Origin Resource Sharing in Rack apps'
         | 
| 14 | 
            +
              spec.homepage      = 'https://github.com/cyu/rack-cors'
         | 
| 15 | 
            +
              spec.license       = 'MIT'
         | 
| 15 16 |  | 
| 16 | 
            -
              spec.files         = `git ls-files`.split( | 
| 17 | 
            +
              spec.files         = `git ls-files`.split($INPUT_RECORD_SEPARATOR).reject { |f| (f == '.gitignore') || f =~ /^examples/ }
         | 
| 17 18 | 
             
              spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
         | 
| 18 19 | 
             
              spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
         | 
| 19 | 
            -
              spec.require_paths = [ | 
| 20 | 
            +
              spec.require_paths = ['lib']
         | 
| 20 21 |  | 
| 21 | 
            -
              spec.add_dependency  | 
| 22 | 
            -
              spec.add_development_dependency  | 
| 23 | 
            -
              spec.add_development_dependency  | 
| 24 | 
            -
              spec.add_development_dependency  | 
| 25 | 
            -
              spec.add_development_dependency  | 
| 26 | 
            -
              spec.add_development_dependency  | 
| 22 | 
            +
              spec.add_dependency 'rack', '>= 2.0.0'
         | 
| 23 | 
            +
              spec.add_development_dependency 'bundler', '>= 1.16.0', '< 3'
         | 
| 24 | 
            +
              spec.add_development_dependency 'minitest', '~> 5.11.0'
         | 
| 25 | 
            +
              spec.add_development_dependency 'mocha', '~> 1.6.0'
         | 
| 26 | 
            +
              spec.add_development_dependency 'pry', '~> 0.12'
         | 
| 27 | 
            +
              spec.add_development_dependency 'rack-test', '~> 1.1.0'
         | 
| 28 | 
            +
              spec.add_development_dependency 'rake', '~> 12.3.0'
         | 
| 29 | 
            +
              spec.add_development_dependency 'rubocop', '~> 0.80.1'
         | 
| 27 30 | 
             
            end
         | 
    
        data/test/.rubocop.yml
    ADDED
    
    
    
        data/test/cors/test.cors.coffee
    CHANGED
    
    | @@ -1,4 +1,6 @@ | |
| 1 | 
            -
            CORS_SERVER = '127.0.0.1.xip.io: | 
| 1 | 
            +
            CORS_SERVER = '127.0.0.1.xip.io:3000'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            mocha.setup({ignoreLeaks: true});
         | 
| 2 4 |  | 
| 3 5 | 
             
            describe 'CORS', ->
         | 
| 4 6 |  | 
| @@ -34,7 +36,7 @@ describe 'CORS', -> | |
| 34 36 |  | 
| 35 37 | 
             
              it 'should allow access to static resource', (done) ->
         | 
| 36 38 | 
             
                $.get "http://#{CORS_SERVER}/static.txt", (data, status, xhr) ->
         | 
| 37 | 
            -
                  expect($.trim(data)).to.eql(" | 
| 39 | 
            +
                  expect($.trim(data)).to.eql("Hello world")
         | 
| 38 40 | 
             
                  done()
         | 
| 39 41 |  | 
| 40 42 | 
             
              it 'should allow post resource', (done) ->
         | 
    
        data/test/cors/test.cors.js
    CHANGED
    
    | @@ -2,7 +2,11 @@ | |
| 2 2 | 
             
            (function() {
         | 
| 3 3 | 
             
              var CORS_SERVER;
         | 
| 4 4 |  | 
| 5 | 
            -
              CORS_SERVER = '127.0.0.1.xip.io: | 
| 5 | 
            +
              CORS_SERVER = '127.0.0.1.xip.io:3000';
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              mocha.setup({
         | 
| 8 | 
            +
                ignoreLeaks: true
         | 
| 9 | 
            +
              });
         | 
| 6 10 |  | 
| 7 11 | 
             
              describe('CORS', function() {
         | 
| 8 12 | 
             
                it('should allow access to dynamic resource', function(done) {
         | 
| @@ -53,7 +57,7 @@ | |
| 53 57 | 
             
                });
         | 
| 54 58 | 
             
                it('should allow access to static resource', function(done) {
         | 
| 55 59 | 
             
                  return $.get(`http://${CORS_SERVER}/static.txt`, function(data, status, xhr) {
         | 
| 56 | 
            -
                    expect($.trim(data)).to.eql(" | 
| 60 | 
            +
                    expect($.trim(data)).to.eql("Hello world");
         | 
| 57 61 | 
             
                    return done();
         | 
| 58 62 | 
             
                  });
         | 
| 59 63 | 
             
                });
         |