├── VERSION ├── .gitignore ├── test ├── test_helper.rb ├── app │ ├── all_routes.rb │ └── test_app.rb └── test_all.rb ├── Rakefile ├── LICENSE ├── sinatra-cross_origin.gemspec ├── lib └── sinatra │ └── cross_origin.rb └── README.markdown /VERSION: -------------------------------------------------------------------------------- 1 | 0.4.0 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/ 2 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../lib/sinatra/cross_origin') 2 | require 'app/test_app' 3 | require 'app/all_routes' 4 | require 'test/unit' 5 | require 'rack/test' 6 | -------------------------------------------------------------------------------- /test/app/all_routes.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | 3 | class AllRoutesApp < Sinatra::Base 4 | register Sinatra::CrossOrigin 5 | enable :cross_origin 6 | 7 | get '/' do 8 | "Hello" 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'jeweler' 3 | Jeweler::Tasks.new do |gemspec| 4 | gemspec.name = "sinatra-cross_origin" 5 | gemspec.summary = "Cross Origin Resource Sharing helper for Sinatra" 6 | gemspec.description = gemspec.summary 7 | gemspec.email = "brit@britg.com" 8 | gemspec.homepage = "http://github.com/britg/sinatra-cross_origin" 9 | gemspec.authors = ["Brit Gardner"] 10 | end 11 | Jeweler::GemcutterTasks.new 12 | rescue LoadError 13 | puts "Jeweler not available. Install it with: gem install jeweler" 14 | end 15 | 16 | require 'rake/testtask' 17 | 18 | Rake::TestTask.new do |t| 19 | t.libs << "test" 20 | t.test_files = FileList['test/test_all.rb'] 21 | t.verbose = true 22 | end 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007, 2008, 2009 Brit Gardner 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /sinatra-cross_origin.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | # stub: sinatra-cross_origin 0.4.0 ruby lib 6 | 7 | Gem::Specification.new do |s| 8 | s.name = "sinatra-cross_origin" 9 | s.version = "0.4.0" 10 | 11 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 12 | s.require_paths = ["lib"] 13 | s.authors = ["Brit Gardner"] 14 | s.date = "2016-10-20" 15 | s.description = "Cross Origin Resource Sharing helper for Sinatra" 16 | s.email = "brit@britg.com" 17 | s.extra_rdoc_files = [ 18 | "LICENSE", 19 | "README.markdown" 20 | ] 21 | s.files = [ 22 | "LICENSE", 23 | "README.markdown", 24 | "Rakefile", 25 | "VERSION", 26 | "lib/sinatra/cross_origin.rb", 27 | "sinatra-cross_origin.gemspec", 28 | "test/app/all_routes.rb", 29 | "test/app/test_app.rb", 30 | "test/test_all.rb", 31 | "test/test_helper.rb" 32 | ] 33 | s.homepage = "http://github.com/britg/sinatra-cross_origin" 34 | s.rubygems_version = "2.4.5.1" 35 | s.summary = "Cross Origin Resource Sharing helper for Sinatra" 36 | end 37 | 38 | -------------------------------------------------------------------------------- /test/app/test_app.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | 3 | class TestApp < Sinatra::Base 4 | register Sinatra::CrossOrigin 5 | 6 | get '/' do 7 | "Hello" 8 | end 9 | 10 | get '/defaults' do 11 | cross_origin 12 | "Defaults" 13 | end 14 | 15 | get '/same_origin' do 16 | "Same origin!" 17 | end 18 | 19 | get '/allow_any_origin' do 20 | cross_origin 21 | "Allowing any origin" 22 | end 23 | 24 | get '/allow_specific_origin' do 25 | cross_origin :allow_origin => 'http://example.com' 26 | "Allowing any origin" 27 | end 28 | 29 | get '/allow_multiple_origins' do 30 | cross_origin :allow_origin => ['http://example.com', 'http://example2.com'] 31 | "Allowing multiple origins" 32 | end 33 | 34 | get '/allow_multiple_origins_with_reg_ex' do 35 | cross_origin :allow_origin => ['http://example.com', /http:\/\/(?:sub-1|sub-2)\.example\.com/] 36 | "Allowing multiple origins with regular expressions" 37 | end 38 | 39 | get '/allow_methods' do 40 | cross_origin :allow_methods => params[:methods].split(', ') 41 | "Allowing methods" 42 | end 43 | 44 | get '/allow_headers' do 45 | cross_origin :allow_headers => params[:allow_headers] 46 | "Allowing headers" 47 | end 48 | 49 | get '/dont_allow_credentials' do 50 | cross_origin :allow_credentials => false 51 | "Not allowing credentials" 52 | end 53 | 54 | get '/set_max_age' do 55 | cross_origin :max_age => params[:maxage] 56 | "Setting max age" 57 | end 58 | 59 | end 60 | -------------------------------------------------------------------------------- /lib/sinatra/cross_origin.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | 3 | # Helper to enable cross origin requests. 4 | # More on Cross Origin Resource Sharing here: 5 | # https://developer.mozilla.org/En/HTTP_access_control 6 | # 7 | # To enable cross origin requests for all routes: 8 | # configure do 9 | # enable :cross_origin 10 | # ... 11 | # 12 | # To enable cross origin requests for a single domain: 13 | # get '/' do 14 | # cross_origin 15 | # ... 16 | # 17 | # More info at: 18 | # http://github.com/britg/sinatra-cross_origin 19 | # 20 | 21 | module Sinatra 22 | module CrossOrigin 23 | module Helpers 24 | 25 | # Apply cross origin headers either 26 | # from global config or custom config passed 27 | # as a hash 28 | def cross_origin(hash=nil) 29 | request_origin = request.env['HTTP_ORIGIN'] 30 | return unless request_origin 31 | settings.set hash if hash 32 | 33 | if settings.allow_origin == :any 34 | origin = request_origin 35 | else 36 | allowed_origins = *settings.allow_origin # make sure its an array 37 | origin = allowed_origins.join('|') # we'll return this unless allowed 38 | 39 | allowed_origins.each do |allowed_origin| 40 | if allowed_origin.is_a?(Regexp) ? 41 | request_origin =~ allowed_origin : 42 | request_origin == allowed_origin 43 | origin = request_origin 44 | break 45 | end 46 | end 47 | end 48 | 49 | methods = settings.allow_methods.map{ |m| m.to_s.upcase! }.join(', ') 50 | 51 | headers_list = { 52 | 'Access-Control-Allow-Origin' => origin, 53 | 'Access-Control-Allow-Methods' => methods, 54 | 'Access-Control-Allow-Headers' => settings.allow_headers.map(&:to_s).join(', '), 55 | 'Access-Control-Allow-Credentials' => settings.allow_credentials.to_s, 56 | 'Access-Control-Max-Age' => settings.max_age.to_s, 57 | 'Access-Control-Expose-Headers' => settings.expose_headers.join(', ') 58 | } 59 | 60 | headers headers_list 61 | end 62 | end 63 | 64 | def self.registered(app) 65 | 66 | app.helpers CrossOrigin::Helpers 67 | 68 | app.set :cross_origin, false 69 | app.set :allow_origin, :any 70 | app.set :allow_methods, [:post, :get, :options] 71 | app.set :allow_credentials, true 72 | app.set :allow_headers, ["*", "Content-Type", "Accept", "AUTHORIZATION", "Cache-Control"] 73 | app.set :max_age, 1728000 74 | app.set :expose_headers, ['Cache-Control', 'Content-Language', 'Content-Type', 'Expires', 'Last-Modified', 'Pragma'] 75 | 76 | app.before do 77 | cross_origin if settings.cross_origin 78 | end 79 | 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | ## About 2 | A simple Sinatra extension to enable Cross Domain Resource Sharing (CORS) 3 | To see more on cross domain resource sharing, see https://developer.mozilla.org/En/HTTP_access_control 4 | 5 | ## Installation with Bundler 6 | gem "sinatra-cross_origin", "~> 0.3.1" 7 | 8 | ## Examples 9 | 10 | To enable cross origin requests for all routes: 11 | 12 | require 'sinatra' 13 | require 'sinatra/cross_origin' 14 | 15 | configure do 16 | enable :cross_origin 17 | end 18 | 19 | To only enable cross origin requests for certain routes: 20 | 21 | get '/cross_origin' do 22 | cross_origin 23 | "This is available to cross-origin javascripts" 24 | end 25 | 26 | If you're using a modular application, remember to register this extension: 27 | 28 | ``` ruby 29 | register Sinatra::CrossOrigin 30 | ``` 31 | 32 | ## Global Configuration 33 | 34 | You can set global options via the normal Sinatra set method: 35 | 36 | set :allow_origin, :any 37 | set :allow_methods, [:get, :post, :options] 38 | set :allow_credentials, true 39 | set :max_age, "1728000" 40 | set :expose_headers, ['Content-Type'] 41 | 42 | You can change configuration options on the fly within routes with: 43 | 44 | get '/custom' do 45 | cross_origin :allow_origin => 'http://example.com', 46 | :allow_methods => [:get], 47 | :allow_credentials => false, 48 | :max_age => "60" 49 | 50 | "My custom cross origin response" 51 | end 52 | 53 | ## Responding to `OPTIONS` 54 | Many browsers send an `OPTIONS` request to a server before performing a CORS request (this is part of [the specification for CORS](http://www.w3.org/TR/cors/) ). These sorts of requests are called [preflight requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests). Without a valid response to an `OPTIONS` request, a browser may refuse to make a CORS request (and complain that the CORS request violates the same-origin policy). 55 | 56 | Currently, this gem does not properly respond to `OPTIONS` requests. See [this issue](https://github.com/britg/sinatra-cross_origin/issues/18). You may have to add code like this in order to make your app properly respond to `OPTIONS` requests: 57 | 58 | options "*" do 59 | response.headers["Allow"] = "HEAD,GET,PUT,POST,DELETE,OPTIONS" 60 | 61 | response.headers["Access-Control-Allow-Headers"] = "X-Requested-With, X-HTTP-Method-Override, Content-Type, Cache-Control, Accept" 62 | 63 | 200 64 | end 65 | 66 | 67 | 68 | ## License 69 | Copyright (c) 2007, 2008, 2009 Brit Gardner 70 | 71 | Permission is hereby granted, free of charge, to any person 72 | obtaining a copy of this software and associated documentation 73 | files (the "Software"), to deal in the Software without 74 | restriction, including without limitation the rights to use, 75 | copy, modify, merge, publish, distribute, sublicense, and/or sell 76 | copies of the Software, and to permit persons to whom the 77 | Software is furnished to do so, subject to the following 78 | conditions: 79 | 80 | The above copyright notice and this permission notice shall be 81 | included in all copies or substantial portions of the Software. 82 | 83 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 84 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 85 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 86 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 87 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 88 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 89 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 90 | OTHER DEALINGS IN THE SOFTWARE. 91 | -------------------------------------------------------------------------------- /test/test_all.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class HelloTest < Test::Unit::TestCase 4 | include Rack::Test::Methods 5 | 6 | def app 7 | TestApp 8 | end 9 | 10 | def _defaults 11 | get '/defaults', {}, {'HTTP_ORIGIN' => 'http://localhost'} 12 | assert last_response.ok? 13 | assert_equal 'http://localhost', last_response.headers['Access-Control-Allow-Origin'] 14 | assert_equal 'POST, GET, OPTIONS', last_response.headers['Access-Control-Allow-Methods'] 15 | assert_equal 'true', last_response.headers['Access-Control-Allow-Credentials'] 16 | assert_equal "1728000", last_response.headers['Access-Control-Max-Age'] 17 | assert_equal false, last_response.headers.has_key?('Access-Control-Allow-Headers') 18 | end 19 | 20 | def test_it_says_hello 21 | get '/' 22 | assert last_response.ok? 23 | assert_equal 'Hello', last_response.body 24 | end 25 | 26 | def test_cross_origin_is_silent_on_same_origin_request 27 | get '/same_origin' 28 | assert last_response.ok? 29 | assert_equal nil, last_response.headers['Access-Control-Allow-Origin'] 30 | end 31 | 32 | def test_allow_any_origin 33 | get '/allow_any_origin', {}, {'HTTP_ORIGIN' => 'http://localhost'} 34 | assert last_response.ok? 35 | assert_equal 'http://localhost', last_response.headers['Access-Control-Allow-Origin'] 36 | end 37 | 38 | def test_allow_specific_origin 39 | get '/allow_specific_origin', {}, {'HTTP_ORIGIN' => 'http://example.com'} 40 | assert last_response.ok? 41 | assert_equal 'http://example.com', last_response.headers['Access-Control-Allow-Origin'] 42 | end 43 | 44 | def test_allow_multiple_origins 45 | get '/allow_multiple_origins', {}, {'HTTP_ORIGIN' => 'http://example.com'} 46 | assert last_response.ok? 47 | assert_equal 'http://example.com', last_response.headers['Access-Control-Allow-Origin'] 48 | 49 | get '/allow_multiple_origins', {}, {'HTTP_ORIGIN' => 'http://example2.com'} 50 | assert last_response.ok? 51 | assert_equal 'http://example2.com', last_response.headers['Access-Control-Allow-Origin'] 52 | 53 | get '/allow_multiple_origins', {}, {'HTTP_ORIGIN' => 'http://example3.com'} 54 | assert last_response.ok? 55 | assert_equal 'http://example.com|http://example2.com', last_response.headers['Access-Control-Allow-Origin'] 56 | end 57 | 58 | def test_allow_multiple_origins_with_reg_ex 59 | get '/allow_multiple_origins_with_reg_ex', {}, {'HTTP_ORIGIN' => 'http://example.com'} 60 | assert last_response.ok? 61 | assert_equal 'http://example.com', last_response.headers['Access-Control-Allow-Origin'] 62 | 63 | get '/allow_multiple_origins_with_reg_ex', {}, {'HTTP_ORIGIN' => 'http://sub-1.example.com'} 64 | assert last_response.ok? 65 | assert_equal 'http://sub-1.example.com', last_response.headers['Access-Control-Allow-Origin'] 66 | end 67 | 68 | def test_allow_methods 69 | get '/allow_methods', {:methods=>'get, post'}, {'HTTP_ORIGIN' => 'http://localhost'} 70 | assert last_response.ok? 71 | assert_equal 'GET, POST', last_response.headers['Access-Control-Allow-Methods'] 72 | end 73 | 74 | def test_allow_headers 75 | get '/allow_headers', {:allow_headers=>['Content-Type', 'Origin', 'Accept']}, {'HTTP_ORIGIN' => 'http://localhost'} 76 | assert last_response.ok? 77 | assert_equal 'Content-Type, Origin, Accept', last_response.headers['Access-Control-Allow-Headers'] 78 | end 79 | 80 | def test_dont_allow_credentials 81 | get '/dont_allow_credentials', {}, {'HTTP_ORIGIN' => 'http://localhost'} 82 | assert last_response.ok? 83 | assert_equal "false", last_response.headers['Access-Control-Allow-Credentials'] 84 | end 85 | 86 | def test_set_max_age 87 | get '/set_max_age', {:maxage=>"60"}, {'HTTP_ORIGIN' => 'http://localhost'} 88 | assert last_response.ok? 89 | assert_equal "60", last_response.headers['Access-Control-Max-Age'] 90 | end 91 | 92 | end 93 | 94 | class AllRoutesTest < Test::Unit::TestCase 95 | include Rack::Test::Methods 96 | 97 | def app 98 | AllRoutesApp 99 | end 100 | 101 | def test_it_says_hello 102 | get '/' 103 | assert last_response.ok? 104 | assert_equal 'Hello', last_response.body 105 | end 106 | 107 | def test_cross_origin_is_silent_on_same_origin_request 108 | get '/' 109 | assert last_response.ok? 110 | assert_equal nil, last_response.headers['Access-Control-Allow-Origin'] 111 | end 112 | 113 | def test_allow_any_origin 114 | get '/', {}, {'HTTP_ORIGIN' => 'http://localhost'} 115 | assert last_response.ok? 116 | assert_equal 'http://localhost', last_response.headers['Access-Control-Allow-Origin'] 117 | end 118 | 119 | end 120 | --------------------------------------------------------------------------------