rack-cors 0.4.1 → 1.0.5
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 +5 -5
- data/.travis.yml +5 -3
- data/CHANGELOG.md +73 -0
- data/Gemfile +1 -1
- data/README.md +23 -19
- data/lib/rack/cors/version.rb +1 -1
- data/lib/rack/cors.rb +125 -83
- data/rack-cors.gemspec +7 -6
- data/test/cors/test.cors.coffee +6 -1
- data/test/cors/test.cors.js +17 -9
- data/test/unit/cors_test.rb +222 -57
- data/test/unit/dsl_test.rb +11 -0
- data/test/unit/insecure.ru +8 -0
- data/test/unit/test.ru +12 -0
- metadata +47 -27
- 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: a12cdfc5aca2abf0cf86fb1ca217619fa6b40cad19721118016e064554f46ba0
         | 
| 4 | 
            +
              data.tar.gz: 2874199b748909fdfd3e8ec601bd8620bc0235e60c66226259a79ff2404dbaf8
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 2b71fe191ad396ab85e8c1966e979fa3516ee768bae6ed93fd1d43644eada8a455dbab00990ef22440ee7f82dab16a37b283897403d4eba674547bda1f0b86f5
         | 
| 7 | 
            +
              data.tar.gz: a31481b3f6d9d45bdc522c444e923438f7f513a57796bf2cf6eaaa665d87f7479bf5f1e5f5ea8d380ce7194f0d3690823e1a681e55c17317cab29bf87b7a7303
         | 
    
        data/.travis.yml
    CHANGED
    
    
    
        data/CHANGELOG.md
    ADDED
    
    | @@ -0,0 +1,73 @@ | |
| 1 | 
            +
            # Change Log
         | 
| 2 | 
            +
            All notable changes to this project will be documented in this file.
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            ## 1.0.5 - 2019-11-14
         | 
| 5 | 
            +
            ### Changed
         | 
| 6 | 
            +
            - Update Gem spec to require rack >= 1.6.0
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            ## 1.0.4 - 2019-11-13
         | 
| 9 | 
            +
            ### Security
         | 
| 10 | 
            +
            - Escape and resolve path before evaluating resource rules (thanks to Colby Morgan)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            ## 1.0.3 - 2019-03-24
         | 
| 13 | 
            +
            ### Changed
         | 
| 14 | 
            +
            - Don't send 'Content-Type' header with pre-flight requests
         | 
| 15 | 
            +
            - Allow ruby array for  vary header config
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            ## 1.0.2 - 2017-10-22
         | 
| 18 | 
            +
            ### Fixed
         | 
| 19 | 
            +
            - Automatically allow simple headers when headers are set
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            ## 1.0.1 - 2017-07-18
         | 
| 22 | 
            +
            ### Fixed
         | 
| 23 | 
            +
            - Allow lambda origin configuration
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            ## 1.0.0 - 2017-07-15
         | 
| 26 | 
            +
            ### Security
         | 
| 27 | 
            +
            - Don't implicitly accept 'null' origins when 'file://' is specified
         | 
| 28 | 
            +
            (https://github.com/cyu/rack-cors/pull/134)
         | 
| 29 | 
            +
            - Ignore '' origins (https://github.com/cyu/rack-cors/issues/139)
         | 
| 30 | 
            +
            - Default credentials option on resources to false
         | 
| 31 | 
            +
            (https://github.com/cyu/rack-cors/issues/95)
         | 
| 32 | 
            +
            - Don't allow credentials option to be true if '*' is specified is origin
         | 
| 33 | 
            +
            (https://github.com/cyu/rack-cors/pull/142)
         | 
| 34 | 
            +
            - Don't reflect Origin header when '*' is specified as origin
         | 
| 35 | 
            +
            (https://github.com/cyu/rack-cors/pull/142)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            ### Fixed
         | 
| 38 | 
            +
            - Don't respond immediately on non-matching preflight requests instead of
         | 
| 39 | 
            +
            sending them through the app (https://github.com/cyu/rack-cors/pull/106)
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            ## 0.4.1 - 2017-02-01
         | 
| 42 | 
            +
            ### Fixed
         | 
| 43 | 
            +
            - Return miss result in X-Rack-CORS instead of incorrectly returning
         | 
| 44 | 
            +
            preflight-hit
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            ## 0.4.0 - 2015-04-15
         | 
| 47 | 
            +
            ### Changed
         | 
| 48 | 
            +
            - Don't set HTTP_ORIGIN with HTTP_X_ORIGIN if nil
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            ### Added
         | 
| 51 | 
            +
            - Calculate vary headers for non-CORS resources
         | 
| 52 | 
            +
            - Support custom vary headers for resource
         | 
| 53 | 
            +
            - Support :if option for resource
         | 
| 54 | 
            +
            - Support :any as a possible value for :methods option
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            ### Fixed
         | 
| 57 | 
            +
            - Don't symbolize incoming HTTP request methods
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            ## 0.3.1 - 2014-12-27
         | 
| 60 | 
            +
            ### Changed
         | 
| 61 | 
            +
            - Changed the env key to rack.cors to avoid Rack::Lint warnings
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            ## 0.3.0 - 2014-10-19
         | 
| 64 | 
            +
            ### Added
         | 
| 65 | 
            +
            - Added support for defining a logger with a Proc
         | 
| 66 | 
            +
            - Return a X-Rack-CORS header when in debug mode detailing how Rack::Cors
         | 
| 67 | 
            +
            processed a request
         | 
| 68 | 
            +
            - Added support for non HTTP/HTTPS origins when just a domain is specified
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            ### Changed
         | 
| 71 | 
            +
            - Changed the log level of the fallback logger to DEBUG
         | 
| 72 | 
            +
            - Print warning when attempting to use :any as an allowed method
         | 
| 73 | 
            +
            - Treat incoming `Origin: null` headers as file://
         | 
    
        data/Gemfile
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -13,7 +13,7 @@ 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 |  | 
| @@ -25,31 +25,30 @@ Put something like the code below in `config/application.rb` of your Rails appli | |
| 25 25 | 
             
            ```ruby
         | 
| 26 26 | 
             
            module YourApp
         | 
| 27 27 | 
             
              class Application < Rails::Application
         | 
| 28 | 
            -
             | 
| 29 28 | 
             
                # ...
         | 
| 30 29 |  | 
| 31 | 
            -
                # Rails  | 
| 30 | 
            +
                # Rails 5
         | 
| 32 31 |  | 
| 33 | 
            -
                config.middleware.insert_before 0,  | 
| 32 | 
            +
                config.middleware.insert_before 0, Rack::Cors do
         | 
| 34 33 | 
             
                  allow do
         | 
| 35 34 | 
             
                    origins '*'
         | 
| 36 | 
            -
                    resource '*', : | 
| 35 | 
            +
                    resource '*', headers: :any, methods: [:get, :post, :options]
         | 
| 37 36 | 
             
                  end
         | 
| 38 37 | 
             
                end
         | 
| 39 | 
            -
                
         | 
| 40 | 
            -
                # Rails 5
         | 
| 41 38 |  | 
| 42 | 
            -
                 | 
| 39 | 
            +
                # Rails 3/4
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                config.middleware.insert_before 0, "Rack::Cors" do
         | 
| 43 42 | 
             
                  allow do
         | 
| 44 43 | 
             
                    origins '*'
         | 
| 45 | 
            -
                    resource '*', : | 
| 44 | 
            +
                    resource '*', headers: :any, methods: [:get, :post, :options]
         | 
| 46 45 | 
             
                  end
         | 
| 47 46 | 
             
                end
         | 
| 48 | 
            -
             | 
| 49 47 | 
             
              end
         | 
| 50 48 | 
             
            end
         | 
| 51 49 | 
             
            ```
         | 
| 52 | 
            -
             | 
| 50 | 
            +
             | 
| 51 | 
            +
            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). 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).
         | 
| 53 52 |  | 
| 54 53 | 
             
            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 54 |  | 
| @@ -68,16 +67,22 @@ use Rack::Cors do | |
| 68 67 |  | 
| 69 68 | 
             
                resource '/file/list_all/', :headers => 'x-domain-token'
         | 
| 70 69 | 
             
                resource '/file/at/*',
         | 
| 71 | 
            -
                    : | 
| 72 | 
            -
                    : | 
| 73 | 
            -
                    : | 
| 74 | 
            -
                    : | 
| 70 | 
            +
                    methods: [:get, :post, :delete, :put, :patch, :options, :head],
         | 
| 71 | 
            +
                    headers: 'x-domain-token',
         | 
| 72 | 
            +
                    expose: ['Some-Custom-Response-Header'],
         | 
| 73 | 
            +
                    max_age: 600
         | 
| 75 74 | 
             
                    # headers to expose
         | 
| 76 75 | 
             
              end
         | 
| 77 76 |  | 
| 78 77 | 
             
              allow do
         | 
| 79 78 | 
             
                origins '*'
         | 
| 80 | 
            -
                resource '/public/*', : | 
| 79 | 
            +
                resource '/public/*', headers: :any, methods: :get
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                # Only allow a request for a specific host
         | 
| 82 | 
            +
                resource '/api/v1/*',
         | 
| 83 | 
            +
                    headers: :any,
         | 
| 84 | 
            +
                    methods: :get,
         | 
| 85 | 
            +
                    if: proc { |env| env['HTTP_HOST'] == 'api.example.com' }
         | 
| 81 86 | 
             
              end
         | 
| 82 87 | 
             
            end
         | 
| 83 88 | 
             
            ```
         | 
| @@ -98,13 +103,12 @@ Additionally, origins can be specified dynamically via a block of the following | |
| 98 103 | 
             
              origins { |source, env| true || false }
         | 
| 99 104 | 
             
            ```
         | 
| 100 105 |  | 
| 101 | 
            -
             | 
| 102 | 
            -
            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:
         | 
| 106 | 
            +
            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:
         | 
| 103 107 |  | 
| 104 108 | 
             
            * **methods** (string or array or `:any`): The HTTP methods allowed for the resource.
         | 
| 105 109 | 
             
            * **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 110 | 
             
            * **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.
         | 
| 111 | 
            +
            * **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 112 | 
             
            * **max_age** (number): Sets the `Access-Control-Max-Age` response header.
         | 
| 109 113 | 
             
            * **if** (Proc): If the result of the proc is true, will process the request as a valid CORS request.
         | 
| 110 114 | 
             
            * **vary** (string or array): A list of HTTP headers to add to the 'Vary' header.
         | 
    
        data/lib/rack/cors/version.rb
    CHANGED
    
    
    
        data/lib/rack/cors.rb
    CHANGED
    
    | @@ -2,13 +2,28 @@ require 'logger' | |
| 2 2 |  | 
| 3 3 | 
             
            module Rack
         | 
| 4 4 | 
             
              class Cors
         | 
| 5 | 
            -
                 | 
| 5 | 
            +
                HTTP_ORIGIN   = 'HTTP_ORIGIN'.freeze
         | 
| 6 | 
            +
                HTTP_X_ORIGIN = 'HTTP_X_ORIGIN'.freeze
         | 
| 6 7 |  | 
| 7 | 
            -
                 | 
| 8 | 
            -
                 | 
| 9 | 
            -
             | 
| 10 | 
            -
                 | 
| 11 | 
            -
                 | 
| 8 | 
            +
                HTTP_ACCESS_CONTROL_REQUEST_METHOD  = 'HTTP_ACCESS_CONTROL_REQUEST_METHOD'.freeze
         | 
| 9 | 
            +
                HTTP_ACCESS_CONTROL_REQUEST_HEADERS = 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS'.freeze
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                PATH_INFO      = 'PATH_INFO'.freeze
         | 
| 12 | 
            +
                REQUEST_METHOD = 'REQUEST_METHOD'.freeze
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                RACK_LOGGER = 'rack.logger'.freeze
         | 
| 15 | 
            +
                RACK_CORS   =
         | 
| 16 | 
            +
                # retaining the old key for backwards compatibility
         | 
| 17 | 
            +
                ENV_KEY     = 'rack.cors'.freeze
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                OPTIONS     = 'OPTIONS'.freeze
         | 
| 20 | 
            +
                VARY        = 'Vary'.freeze
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                DEFAULT_VARY_HEADERS = ['Origin'].freeze
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                # All CORS routes need to accept CORS simple headers at all times
         | 
| 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
         | 
| 12 27 |  | 
| 13 28 | 
             
                def initialize(app, opts={}, &block)
         | 
| 14 29 | 
             
                  @app = app
         | 
| @@ -47,36 +62,38 @@ module Rack | |
| 47 62 | 
             
                end
         | 
| 48 63 |  | 
| 49 64 | 
             
                def call(env)
         | 
| 50 | 
            -
                  env[ | 
| 65 | 
            +
                  env[HTTP_ORIGIN] ||= env[HTTP_X_ORIGIN] if env[HTTP_X_ORIGIN]
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  path = evaluate_path(env)
         | 
| 51 68 |  | 
| 52 69 | 
             
                  add_headers = nil
         | 
| 53 | 
            -
                  if env[ | 
| 70 | 
            +
                  if env[HTTP_ORIGIN]
         | 
| 54 71 | 
             
                    debug(env) do
         | 
| 55 72 | 
             
                      [ 'Incoming Headers:',
         | 
| 56 | 
            -
                        "  Origin: #{env[ | 
| 57 | 
            -
                        "   | 
| 58 | 
            -
                        "  Access-Control-Request- | 
| 73 | 
            +
                        "  Origin: #{env[HTTP_ORIGIN]}",
         | 
| 74 | 
            +
                        "  Path-Info: #{path}",
         | 
| 75 | 
            +
                        "  Access-Control-Request-Method: #{env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]}",
         | 
| 76 | 
            +
                        "  Access-Control-Request-Headers: #{env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]}"
         | 
| 59 77 | 
             
                        ].join("\n")
         | 
| 60 78 | 
             
                    end
         | 
| 61 | 
            -
                    if env[ | 
| 62 | 
            -
                       | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
                        end
         | 
| 67 | 
            -
                        return [200, headers, []]
         | 
| 79 | 
            +
                    if env[REQUEST_METHOD] == OPTIONS and env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]
         | 
| 80 | 
            +
                      headers = process_preflight(env, path)
         | 
| 81 | 
            +
                      debug(env) do
         | 
| 82 | 
            +
                        "Preflight Headers:\n" +
         | 
| 83 | 
            +
                            headers.collect{|kv| "  #{kv.join(': ')}"}.join("\n")
         | 
| 68 84 | 
             
                      end
         | 
| 85 | 
            +
                      return [200, headers, []]
         | 
| 69 86 | 
             
                    else
         | 
| 70 | 
            -
                      add_headers = process_cors(env)
         | 
| 87 | 
            +
                      add_headers = process_cors(env, path)
         | 
| 71 88 | 
             
                    end
         | 
| 72 89 | 
             
                  else
         | 
| 73 90 | 
             
                    Result.miss(env, Result::MISS_NO_ORIGIN)
         | 
| 74 91 | 
             
                  end
         | 
| 75 92 |  | 
| 76 93 | 
             
                  # This call must be done BEFORE calling the app because for some reason
         | 
| 77 | 
            -
                  # env[ | 
| 78 | 
            -
                  #  | 
| 79 | 
            -
                  vary_resource = resource_for_path( | 
| 94 | 
            +
                  # env[PATH_INFO] gets changed after that and it won't match. (At least
         | 
| 95 | 
            +
                  # in rails 4.1.6)
         | 
| 96 | 
            +
                  vary_resource = resource_for_path(path)
         | 
| 80 97 |  | 
| 81 98 | 
             
                  status, headers, body = @app.call env
         | 
| 82 99 |  | 
| @@ -95,16 +112,16 @@ module Rack | |
| 95 112 | 
             
                  # response to be different depending on the Origin header value.
         | 
| 96 113 | 
             
                  # Better explained here: http://www.fastly.com/blog/best-practices-for-using-the-vary-header/
         | 
| 97 114 | 
             
                  if vary_resource
         | 
| 98 | 
            -
                    vary = headers[ | 
| 115 | 
            +
                    vary = headers[VARY]
         | 
| 99 116 | 
             
                    cors_vary_headers = if vary_resource.vary_headers && vary_resource.vary_headers.any?
         | 
| 100 117 | 
             
                      vary_resource.vary_headers
         | 
| 101 118 | 
             
                    else
         | 
| 102 119 | 
             
                      DEFAULT_VARY_HEADERS
         | 
| 103 120 | 
             
                    end
         | 
| 104 | 
            -
                    headers[ | 
| 121 | 
            +
                    headers[VARY] = ((vary ? ([vary].flatten.map { |v| v.split(/,\s*/) }.flatten) : []) + cors_vary_headers).uniq.join(', ')
         | 
| 105 122 | 
             
                  end
         | 
| 106 123 |  | 
| 107 | 
            -
                  if debug? && result = env[ | 
| 124 | 
            +
                  if debug? && result = env[RACK_CORS]
         | 
| 108 125 | 
             
                    result.append_header(headers)
         | 
| 109 126 | 
             
                  end
         | 
| 110 127 |  | 
| @@ -122,36 +139,41 @@ module Rack | |
| 122 139 | 
             
                      @logger_proc = nil
         | 
| 123 140 | 
             
                      logger_proc.call
         | 
| 124 141 |  | 
| 125 | 
            -
                    elsif defined?(Rails) && Rails.logger
         | 
| 142 | 
            +
                    elsif defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
         | 
| 126 143 | 
             
                      Rails.logger
         | 
| 127 144 |  | 
| 128 | 
            -
                    elsif env[ | 
| 129 | 
            -
                      env[ | 
| 145 | 
            +
                    elsif env[RACK_LOGGER]
         | 
| 146 | 
            +
                      env[RACK_LOGGER]
         | 
| 130 147 |  | 
| 131 148 | 
             
                    else
         | 
| 132 149 | 
             
                      ::Logger.new(STDOUT).tap { |logger| logger.level = ::Logger::Severity::DEBUG }
         | 
| 133 150 | 
             
                    end
         | 
| 134 151 | 
             
                  end
         | 
| 135 152 |  | 
| 153 | 
            +
                  def evaluate_path(env)
         | 
| 154 | 
            +
                    path = env[PATH_INFO]
         | 
| 155 | 
            +
                    path = Rack::Utils.clean_path_info(Rack::Utils.unescape_path(path)) if path
         | 
| 156 | 
            +
                    path
         | 
| 157 | 
            +
                  end
         | 
| 158 | 
            +
             | 
| 136 159 | 
             
                  def all_resources
         | 
| 137 160 | 
             
                    @all_resources ||= []
         | 
| 138 161 | 
             
                  end
         | 
| 139 162 |  | 
| 140 | 
            -
                  def process_preflight(env)
         | 
| 141 | 
            -
                     | 
| 142 | 
            -
                    if resource
         | 
| 143 | 
            -
                      Result.preflight_hit(env)
         | 
| 144 | 
            -
                      preflight = resource.process_preflight(env)
         | 
| 145 | 
            -
                      preflight
         | 
| 163 | 
            +
                  def process_preflight(env, path)
         | 
| 164 | 
            +
                    result = Result.preflight(env)
         | 
| 146 165 |  | 
| 147 | 
            -
                     | 
| 148 | 
            -
             | 
| 149 | 
            -
                       | 
| 166 | 
            +
                    resource, error = match_resource(path, env)
         | 
| 167 | 
            +
                    unless resource
         | 
| 168 | 
            +
                      result.miss(error)
         | 
| 169 | 
            +
                      return {}
         | 
| 150 170 | 
             
                    end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                    return resource.process_preflight(env, result)
         | 
| 151 173 | 
             
                  end
         | 
| 152 174 |  | 
| 153 | 
            -
                  def process_cors(env)
         | 
| 154 | 
            -
                    resource, error = match_resource(env)
         | 
| 175 | 
            +
                  def process_cors(env, path)
         | 
| 176 | 
            +
                    resource, error = match_resource(path, env)
         | 
| 155 177 | 
             
                    if resource
         | 
| 156 178 | 
             
                      Result.hit(env)
         | 
| 157 179 | 
             
                      cors = resource.to_headers(env)
         | 
| @@ -172,9 +194,8 @@ module Rack | |
| 172 194 | 
             
                    nil
         | 
| 173 195 | 
             
                  end
         | 
| 174 196 |  | 
| 175 | 
            -
                  def match_resource(env)
         | 
| 176 | 
            -
                     | 
| 177 | 
            -
                    origin = env[ORIGIN_HEADER_KEY]
         | 
| 197 | 
            +
                  def match_resource(path, env)
         | 
| 198 | 
            +
                    origin = env[HTTP_ORIGIN]
         | 
| 178 199 |  | 
| 179 200 | 
             
                    origin_matched = false
         | 
| 180 201 | 
             
                    all_resources.each do |r|
         | 
| @@ -195,6 +216,10 @@ module Rack | |
| 195 216 | 
             
                    MISS_NO_ORIGIN = 'no-origin'.freeze
         | 
| 196 217 | 
             
                    MISS_NO_PATH   = 'no-path'.freeze
         | 
| 197 218 |  | 
| 219 | 
            +
                    MISS_NO_METHOD   = 'no-method'.freeze
         | 
| 220 | 
            +
                    MISS_DENY_METHOD = 'deny-method'.freeze
         | 
| 221 | 
            +
                    MISS_DENY_HEADER = 'deny-header'.freeze
         | 
| 222 | 
            +
             | 
| 198 223 | 
             
                    attr_accessor :preflight, :hit, :miss_reason
         | 
| 199 224 |  | 
| 200 225 | 
             
                    def hit?
         | 
| @@ -205,11 +230,16 @@ module Rack | |
| 205 230 | 
             
                      !!preflight
         | 
| 206 231 | 
             
                    end
         | 
| 207 232 |  | 
| 233 | 
            +
                    def miss(reason)
         | 
| 234 | 
            +
                      self.hit = false
         | 
| 235 | 
            +
                      self.miss_reason = reason
         | 
| 236 | 
            +
                    end
         | 
| 237 | 
            +
             | 
| 208 238 | 
             
                    def self.hit(env)
         | 
| 209 239 | 
             
                      r = Result.new
         | 
| 210 240 | 
             
                      r.preflight = false
         | 
| 211 241 | 
             
                      r.hit = true
         | 
| 212 | 
            -
                      env[ | 
| 242 | 
            +
                      env[RACK_CORS] = r
         | 
| 213 243 | 
             
                    end
         | 
| 214 244 |  | 
| 215 245 | 
             
                    def self.miss(env, reason)
         | 
| @@ -217,23 +247,15 @@ module Rack | |
| 217 247 | 
             
                      r.preflight = false
         | 
| 218 248 | 
             
                      r.hit = false
         | 
| 219 249 | 
             
                      r.miss_reason = reason
         | 
| 220 | 
            -
                      env[ | 
| 250 | 
            +
                      env[RACK_CORS] = r
         | 
| 221 251 | 
             
                    end
         | 
| 222 252 |  | 
| 223 | 
            -
                    def self. | 
| 253 | 
            +
                    def self.preflight(env)
         | 
| 224 254 | 
             
                      r = Result.new
         | 
| 225 255 | 
             
                      r.preflight = true
         | 
| 226 | 
            -
                       | 
| 227 | 
            -
                      env[ENV_KEY] = r
         | 
| 256 | 
            +
                      env[RACK_CORS] = r
         | 
| 228 257 | 
             
                    end
         | 
| 229 258 |  | 
| 230 | 
            -
                    def self.preflight_miss(env, reason)
         | 
| 231 | 
            -
                      r = Result.new
         | 
| 232 | 
            -
                      r.preflight = true
         | 
| 233 | 
            -
                      r.hit = false
         | 
| 234 | 
            -
                      r.miss_reason = reason
         | 
| 235 | 
            -
                      env[ENV_KEY] = r
         | 
| 236 | 
            -
                    end
         | 
| 237 259 |  | 
| 238 260 | 
             
                    def append_header(headers)
         | 
| 239 261 | 
             
                      headers[HEADER_KEY] = if hit?
         | 
| @@ -248,6 +270,9 @@ module Rack | |
| 248 270 | 
             
                  end
         | 
| 249 271 |  | 
| 250 272 | 
             
                  class Resources
         | 
| 273 | 
            +
             | 
| 274 | 
            +
                    attr_reader :resources
         | 
| 275 | 
            +
             | 
| 251 276 | 
             
                    def initialize
         | 
| 252 277 | 
             
                      @origins = []
         | 
| 253 278 | 
             
                      @resources = []
         | 
| @@ -255,9 +280,10 @@ module Rack | |
| 255 280 | 
             
                    end
         | 
| 256 281 |  | 
| 257 282 | 
             
                    def origins(*args, &blk)
         | 
| 258 | 
            -
                      @origins = args.flatten. | 
| 283 | 
            +
                      @origins = args.flatten.reject{ |s| s == '' }.map do |n|
         | 
| 259 284 | 
             
                        case n
         | 
| 260 | 
            -
                        when  | 
| 285 | 
            +
                        when Proc,
         | 
| 286 | 
            +
                             Regexp,
         | 
| 261 287 | 
             
                             /^https?:\/\//,
         | 
| 262 288 | 
             
                             'file://'        then n
         | 
| 263 289 | 
             
                        when '*'              then @public_resources = true; n
         | 
| @@ -278,13 +304,11 @@ module Rack | |
| 278 304 | 
             
                    def allow_origin?(source,env = {})
         | 
| 279 305 | 
             
                      return true if public_resources?
         | 
| 280 306 |  | 
| 281 | 
            -
                      effective_source = (source == 'null' ? 'file://' : source)
         | 
| 282 | 
            -
             | 
| 283 307 | 
             
                      return !! @origins.detect do |origin|
         | 
| 284 308 | 
             
                        if origin.is_a?(Proc)
         | 
| 285 309 | 
             
                          origin.call(source,env)
         | 
| 286 310 | 
             
                        else
         | 
| 287 | 
            -
                          origin ===  | 
| 311 | 
            +
                          origin === source
         | 
| 288 312 | 
             
                        end
         | 
| 289 313 | 
             
                      end
         | 
| 290 314 | 
             
                    end
         | 
| @@ -300,12 +324,21 @@ module Rack | |
| 300 324 | 
             
                  end
         | 
| 301 325 |  | 
| 302 326 | 
             
                  class Resource
         | 
| 327 | 
            +
                    class CorsMisconfigurationError < StandardError
         | 
| 328 | 
            +
                      def message
         | 
| 329 | 
            +
                        "Allowing credentials for wildcard origins is insecure."\
         | 
| 330 | 
            +
                        " Please specify more restrictive origins or set 'credentials' to false in your CORS configuration."
         | 
| 331 | 
            +
                      end
         | 
| 332 | 
            +
                    end
         | 
| 333 | 
            +
             | 
| 303 334 | 
             
                    attr_accessor :path, :methods, :headers, :expose, :max_age, :credentials, :pattern, :if_proc, :vary_headers
         | 
| 304 335 |  | 
| 305 336 | 
             
                    def initialize(public_resource, path, opts={})
         | 
| 337 | 
            +
                      raise CorsMisconfigurationError if public_resource && opts[:credentials] == true
         | 
| 338 | 
            +
             | 
| 306 339 | 
             
                      self.path         = path
         | 
| 307 | 
            -
                      self.credentials  =  | 
| 308 | 
            -
                      self.max_age      = opts[:max_age] ||  | 
| 340 | 
            +
                      self.credentials  = public_resource ? false : (opts[:credentials] == true)
         | 
| 341 | 
            +
                      self.max_age      = opts[:max_age] || 7200
         | 
| 309 342 | 
             
                      self.pattern      = compile(path)
         | 
| 310 343 | 
             
                      self.if_proc      = opts[:if]
         | 
| 311 344 | 
             
                      self.vary_headers = opts[:vary] && [opts[:vary]].flatten
         | 
| @@ -323,7 +356,7 @@ module Rack | |
| 323 356 | 
             
                      else
         | 
| 324 357 | 
             
                        ensure_enum(opts[:methods]) || [:get]
         | 
| 325 358 | 
             
                      end.map{|e| e.to_s }
         | 
| 326 | 
            -
             | 
| 359 | 
            +
             | 
| 327 360 | 
             
                      self.expose = opts[:expose] ? [opts[:expose]].flatten : nil
         | 
| 328 361 | 
             
                    end
         | 
| 329 362 |  | 
| @@ -335,14 +368,29 @@ module Rack | |
| 335 368 | 
             
                      matches_path?(path) && (if_proc.nil? || if_proc.call(env))
         | 
| 336 369 | 
             
                    end
         | 
| 337 370 |  | 
| 338 | 
            -
                    def process_preflight(env)
         | 
| 339 | 
            -
                       | 
| 340 | 
            -
             | 
| 371 | 
            +
                    def process_preflight(env, result)
         | 
| 372 | 
            +
                      headers = {}
         | 
| 373 | 
            +
             | 
| 374 | 
            +
                      request_method = env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]
         | 
| 375 | 
            +
                      if request_method.nil?
         | 
| 376 | 
            +
                        result.miss(Result::MISS_NO_METHOD) and return headers
         | 
| 377 | 
            +
                      end
         | 
| 378 | 
            +
                      if !methods.include?(request_method.downcase)
         | 
| 379 | 
            +
                        result.miss(Result::MISS_DENY_METHOD) and return headers
         | 
| 380 | 
            +
                      end
         | 
| 381 | 
            +
             | 
| 382 | 
            +
                      request_headers = env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]
         | 
| 383 | 
            +
                      if request_headers && !allow_headers?(request_headers)
         | 
| 384 | 
            +
                        result.miss(Result::MISS_DENY_HEADER) and return headers
         | 
| 385 | 
            +
                      end
         | 
| 386 | 
            +
             | 
| 387 | 
            +
                      result.hit = true
         | 
| 388 | 
            +
                      headers.merge(to_preflight_headers(env))
         | 
| 341 389 | 
             
                    end
         | 
| 342 390 |  | 
| 343 391 | 
             
                    def to_headers(env)
         | 
| 344 392 | 
             
                      h = {
         | 
| 345 | 
            -
                        'Access-Control-Allow-Origin'     => origin_for_response_header(env[ | 
| 393 | 
            +
                        'Access-Control-Allow-Origin'     => origin_for_response_header(env[HTTP_ORIGIN]),
         | 
| 346 394 | 
             
                        'Access-Control-Allow-Methods'    => methods.collect{|m| m.to_s.upcase}.join(', '),
         | 
| 347 395 | 
             
                        'Access-Control-Expose-Headers'   => expose.nil? ? '' : expose.join(', '),
         | 
| 348 396 | 
             
                        'Access-Control-Max-Age'          => max_age.to_s }
         | 
| @@ -356,33 +404,27 @@ module Rack | |
| 356 404 | 
             
                      end
         | 
| 357 405 |  | 
| 358 406 | 
             
                      def origin_for_response_header(origin)
         | 
| 359 | 
            -
                        return '*' if public_resource? | 
| 407 | 
            +
                        return '*' if public_resource?
         | 
| 360 408 | 
             
                        origin
         | 
| 361 409 | 
             
                      end
         | 
| 362 410 |  | 
| 363 411 | 
             
                      def to_preflight_headers(env)
         | 
| 364 412 | 
             
                        h = to_headers(env)
         | 
| 365 | 
            -
                        if env[ | 
| 366 | 
            -
                          h.merge!('Access-Control-Allow-Headers' => env[ | 
| 413 | 
            +
                        if env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]
         | 
| 414 | 
            +
                          h.merge!('Access-Control-Allow-Headers' => env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS])
         | 
| 367 415 | 
             
                        end
         | 
| 368 416 | 
             
                        h
         | 
| 369 417 | 
             
                      end
         | 
| 370 418 |  | 
| 371 | 
            -
                      def invalid_method_request?(env)
         | 
| 372 | 
            -
                        request_method = env['HTTP_ACCESS_CONTROL_REQUEST_METHOD']
         | 
| 373 | 
            -
                        request_method.nil? || !methods.include?(request_method.downcase)
         | 
| 374 | 
            -
                      end
         | 
| 375 | 
            -
             | 
| 376 | 
            -
                      def invalid_headers_request?(env)
         | 
| 377 | 
            -
                        request_headers = env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']
         | 
| 378 | 
            -
                        request_headers && !allow_headers?(request_headers)
         | 
| 379 | 
            -
                      end
         | 
| 380 | 
            -
             | 
| 381 419 | 
             
                      def allow_headers?(request_headers)
         | 
| 382 | 
            -
                         | 
| 383 | 
            -
                        headers == :any | 
| 384 | 
            -
                           | 
| 385 | 
            -
             | 
| 420 | 
            +
                        headers = self.headers || []
         | 
| 421 | 
            +
                        if headers == :any
         | 
| 422 | 
            +
                          return true
         | 
| 423 | 
            +
                        end
         | 
| 424 | 
            +
                        request_headers = request_headers.split(/,\s*/) if request_headers.kind_of?(String)
         | 
| 425 | 
            +
                        request_headers.all? do |header|
         | 
| 426 | 
            +
                          header = header.downcase
         | 
| 427 | 
            +
                          CORS_SIMPLE_HEADERS.include?(header) || headers.include?(header)
         | 
| 386 428 | 
             
                        end
         | 
| 387 429 | 
             
                      end
         | 
| 388 430 |  | 
    
        data/rack-cors.gemspec
    CHANGED
    
    | @@ -8,7 +8,7 @@ Gem::Specification.new do |spec| | |
| 8 8 | 
             
              spec.version       = Rack::Cors::VERSION
         | 
| 9 9 | 
             
              spec.authors       = ["Calvin Yu"]
         | 
| 10 10 | 
             
              spec.email         = ["me@sourcebender.com"]
         | 
| 11 | 
            -
              spec.description   = %q{Middleware that will make Rack-based apps CORS compatible. | 
| 11 | 
            +
              spec.description   = %q{Middleware that will make Rack-based apps CORS compatible. Fork the project here: https://github.com/cyu/rack-cors}
         | 
| 12 12 | 
             
              spec.summary       = %q{Middleware for enabling Cross-Origin Resource Sharing in Rack apps}
         | 
| 13 13 | 
             
              spec.homepage      = "https://github.com/cyu/rack-cors"
         | 
| 14 14 | 
             
              spec.license       = "MIT"
         | 
| @@ -18,9 +18,10 @@ Gem::Specification.new do |spec| | |
| 18 18 | 
             
              spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
         | 
| 19 19 | 
             
              spec.require_paths = ["lib"]
         | 
| 20 20 |  | 
| 21 | 
            -
              spec. | 
| 22 | 
            -
              spec.add_development_dependency " | 
| 23 | 
            -
              spec.add_development_dependency " | 
| 24 | 
            -
              spec.add_development_dependency " | 
| 25 | 
            -
              spec.add_development_dependency " | 
| 21 | 
            +
              spec.add_dependency "rack", ">= 1.6.0"
         | 
| 22 | 
            +
              spec.add_development_dependency "bundler", ">= 1.16.0", '< 3'
         | 
| 23 | 
            +
              spec.add_development_dependency "rake", "~> 12.3.0"
         | 
| 24 | 
            +
              spec.add_development_dependency "minitest", "~> 5.11.0"
         | 
| 25 | 
            +
              spec.add_development_dependency "mocha", "~> 1.6.0"
         | 
| 26 | 
            +
              spec.add_development_dependency "rack-test", "~> 1.1.0"
         | 
| 26 27 | 
             
            end
         | 
    
        data/test/cors/test.cors.coffee
    CHANGED
    
    | @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            CORS_SERVER = ' | 
| 1 | 
            +
            CORS_SERVER = '127.0.0.1.xip.io:9292'
         | 
| 2 2 |  | 
| 3 3 | 
             
            describe 'CORS', ->
         | 
| 4 4 |  | 
| @@ -12,6 +12,11 @@ describe 'CORS', -> | |
| 12 12 | 
             
                  expect(data).to.eql('Hello world')
         | 
| 13 13 | 
             
                  done()
         | 
| 14 14 |  | 
| 15 | 
            +
              it 'should allow PATCH access to dynamic resource', (done) ->
         | 
| 16 | 
            +
                $.ajax("http://#{CORS_SERVER}/", type: 'PATCH').done (data, textStatus, jqXHR) ->
         | 
| 17 | 
            +
                  expect(data).to.eql('Hello world')
         | 
| 18 | 
            +
                  done()
         | 
| 19 | 
            +
             | 
| 15 20 | 
             
              it 'should allow HEAD access to dynamic resource', (done) ->
         | 
| 16 21 | 
             
                $.ajax("http://#{CORS_SERVER}/", type: 'HEAD').done (data, textStatus, jqXHR) ->
         | 
| 17 22 | 
             
                  expect(jqXHR.status).to.eql(200)
         |