pax_global_header00006660000000000000000000000064121163764010014513gustar00rootroot0000000000000052 comment=634fb913d80a01a14a21c1185788db535e1d68dc ruby-rack-cors-0.2.7/000077500000000000000000000000001211637640100143645ustar00rootroot00000000000000ruby-rack-cors-0.2.7/Gemfile000066400000000000000000000002371211637640100156610ustar00rootroot00000000000000source :rubygems gem "rack" group :development do gem "rake" gem "shoulda" gem "rack-test" gem "bundler", "~> 1.1.0" gem "jeweler", "~> 1.8.3" end ruby-rack-cors-0.2.7/LICENSE.txt000066400000000000000000000020351211637640100162070ustar00rootroot00000000000000Copyright (c) 2012 Calvin Yu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ruby-rack-cors-0.2.7/README.rdoc000066400000000000000000000024321211637640100161730ustar00rootroot00000000000000= Rack CORS Middleware Rack::Cors provides support for Cross-Origin Resource Sharing (CORS) for Rack compatible web applications. The CORS spec allows web applications to make cross domain AJAX calls without using workarounds such as JSONP. For a thorough write up on CORS, see this blog post: http://www.nczonline.net/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/ Or for all the gory details, you can read the spec here: http://www.w3.org/TR/access-control/#simple-cross-origin-request-and-actual-r Install the gem: gem install rack-cors In your Gemfile: gem 'rack-cors', :require => 'rack/cors' == Configuration You configure Rack::Cors by passing a block to the use command: use Rack::Cors do allow do origins 'localhost:3000', '127.0.0.1:3000', /http:\/\/192\.168\.0\.\d{1,3}(:\d+)?/ # regular expressions can be used here resource '/file/list_all/', :headers => 'x-domain-token' resource '/file/at/*', :methods => [:get, :post, :put, :delete], :headers => 'x-domain-token', :expose => ['Some-Custom-Response-Header'] # headers to expose end allow do origins '*' resource '/public/*', :headers => :any, :methods => :get end end ruby-rack-cors-0.2.7/Rakefile000066400000000000000000000026041211637640100160330ustar00rootroot00000000000000 # encoding: utf-8 require 'rubygems' require 'bundler' begin Bundler.setup(:default, :development) rescue Bundler::BundlerError => e $stderr.puts e.message $stderr.puts "Run `bundle install` to install missing gems" exit e.status_code end require 'rake' require 'jeweler' Jeweler::Tasks.new do |gem| # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options gem.name = "rack-cors" gem.homepage = "http://github.com/cyu/rack-cors" gem.license = "MIT" gem.summary = "Middleware for enabling Cross-Origin Resource Sharing in Rack apps" gem.description = "Middleware that will make Rack-based apps CORS compatible. Read more here: http://blog.sourcebender.com/2010/06/09/introducin-rack-cors.html. Fork the project here: http://github.com/cyu/rack-cors" gem.email = "me@sourcebender.com" gem.authors = ["Calvin Yu"] # dependencies defined in Gemfile end Jeweler::RubygemsDotOrgTasks.new require 'rake/testtask' Rake::TestTask.new(:test) do |test| test.libs << 'lib' << 'test' test.pattern = 'test/**/*_test.rb' test.verbose = true end task :default => :test require 'rdoc/task' Rake::RDocTask.new do |rdoc| version = File.exist?('VERSION') ? File.read('VERSION') : "" rdoc.rdoc_dir = 'rdoc' rdoc.title = "rack-cors2 #{version}" rdoc.rdoc_files.include('README*') rdoc.rdoc_files.include('lib/**/*.rb') end ruby-rack-cors-0.2.7/VERSION000066400000000000000000000000051211637640100154270ustar00rootroot000000000000000.2.7ruby-rack-cors-0.2.7/lib/000077500000000000000000000000001211637640100151325ustar00rootroot00000000000000ruby-rack-cors-0.2.7/lib/rack/000077500000000000000000000000001211637640100160525ustar00rootroot00000000000000ruby-rack-cors-0.2.7/lib/rack/cors.rb000066400000000000000000000153351211637640100173540ustar00rootroot00000000000000require 'logger' module Rack class Cors def initialize(app, opts={}, &block) @app = app @logger = opts[:logger] if block.arity == 1 block.call(self) else instance_eval(&block) end end def allow(&block) all_resources << (resources = Resources.new) if block.arity == 1 block.call(resources) else resources.instance_eval(&block) end end def call(env) env['HTTP_ORIGIN'] = 'file://' if env['HTTP_ORIGIN'] == 'null' env['HTTP_ORIGIN'] ||= env['HTTP_X_ORIGIN'] cors_headers = nil if env['HTTP_ORIGIN'] debug(env) do [ 'Incoming Headers:', " Origin: #{env['HTTP_ORIGIN']}", " Access-Control-Request-Method: #{env['HTTP_ACCESS_CONTROL_REQUEST_METHOD']}", " Access-Control-Request-Headers: #{env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}" ].join("\n") end if env['REQUEST_METHOD'] == 'OPTIONS' if headers = process_preflight(env) debug(env) do "Preflight Headers:\n" + headers.collect{|kv| " #{kv.join(': ')}"}.join("\n") end return [200, headers, []] end else cors_headers = process_cors(env) end end status, headers, body = @app.call env if cors_headers headers = headers.merge(cors_headers) unless headers['Access-Control-Allow-Origin'] == '*' vary = headers['Vary'] headers['Vary'] = ((vary ? vary.split(/,\s*/) : []) + ['Origin']).uniq.join(', ') end end [status, headers, body] end protected def debug(env, message = nil, &block) logger = @logger || env['rack.logger'] || begin @logger = ::Logger.new(STDOUT).tap {|logger| logger.level = ::Logger::Severity::INFO} end logger.debug(message, &block) end def all_resources @all_resources ||= [] end def process_preflight(env) resource = find_resource(env['HTTP_ORIGIN'], env['PATH_INFO']) resource && resource.process_preflight(env) end def process_cors(env) resource = find_resource(env['HTTP_ORIGIN'], env['PATH_INFO']) resource.to_headers(env) if resource end def find_resource(origin, path) allowed = all_resources.detect {|r| r.allow_origin?(origin)} allowed ? allowed.find_resource(path) : nil end class Resources def initialize @origins = [] @resources = [] @public_resources = false end def origins(*args) @origins = args.flatten.collect do |n| case n when Regexp, /^https?:\/\// then n when '*' then @public_resources = true; n else "http://#{n}" end end end def resource(path, opts={}) @resources << Resource.new(public_resources?, path, opts) end def public_resources? @public_resources end def allow_origin?(source) public_resources? || !!@origins.detect {|origin| origin === source} end def find_resource(path) @resources.detect{|r| r.match?(path)} end end class Resource attr_accessor :path, :methods, :headers, :expose, :max_age, :credentials, :pattern def initialize(public_resource, path, opts={}) self.path = path self.methods = ensure_enum(opts[:methods]) || [:get] self.credentials = opts[:credentials].nil? ? true : opts[:credentials] self.max_age = opts[:max_age] || 1728000 self.pattern = compile(path) @public_resource = public_resource self.headers = case opts[:headers] when :any then :any when nil then nil else [opts[:headers]].flatten.collect{|h| h.downcase} end self.expose = opts[:expose] ? [opts[:expose]].flatten : nil end def match?(path) pattern =~ path end def process_preflight(env) return nil if invalid_method_request?(env) || invalid_headers_request?(env) {'Content-Type' => 'text/plain'}.merge(to_preflight_headers(env)) end def to_headers(env) x_origin = env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'] h = { 'Access-Control-Allow-Origin' => credentials ? env['HTTP_ORIGIN'] : public_resource? ? '*' : env['HTTP_ORIGIN'], 'Access-Control-Allow-Methods' => methods.collect{|m| m.to_s.upcase}.join(', '), 'Access-Control-Expose-Headers' => expose.nil? ? '' : expose.join(', '), 'Access-Control-Max-Age' => max_age.to_s } h['Access-Control-Allow-Credentials'] = 'true' if credentials h end protected def public_resource? @public_resource end def to_preflight_headers(env) h = to_headers(env) if env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'] h.merge!('Access-Control-Allow-Headers' => env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']) end h end def invalid_method_request?(env) request_method = env['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] request_method.nil? || !methods.include?(request_method.downcase.to_sym) end def invalid_headers_request?(env) request_headers = env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'] request_headers && !allow_headers?(request_headers) end def allow_headers?(request_headers) return false if headers.nil? headers == :any || begin request_headers = request_headers.split(/,\s*/) if request_headers.kind_of?(String) request_headers.all?{|h| headers.include?(h.downcase)} end end def ensure_enum(v) return nil if v.nil? [v].flatten end def compile(path) if path.respond_to? :to_str special_chars = %w{. + ( )} pattern = path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match| case match when "*" "(.*?)" when *special_chars Regexp.escape(match) else "([^/?&#]+)" end end /^#{pattern}$/ elsif path.respond_to? :match path else raise TypeError, path end end end end end ruby-rack-cors-0.2.7/metadata.yml000066400000000000000000000072651211637640100167010ustar00rootroot00000000000000--- !ruby/object:Gem::Specification name: rack-cors version: !ruby/object:Gem::Version version: 0.2.7 prerelease: platform: ruby authors: - Calvin Yu autorequire: bindir: bin cert_chain: [] date: 2012-06-07 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rack requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: shoulda requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rack-test requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: bundler requirement: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: 1.1.0 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: 1.1.0 - !ruby/object:Gem::Dependency name: jeweler requirement: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: 1.8.3 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: 1.8.3 description: ! 'Middleware that will make Rack-based apps CORS compatible. Read more here: http://blog.sourcebender.com/2010/06/09/introducin-rack-cors.html. Fork the project here: http://github.com/cyu/rack-cors' email: me@sourcebender.com executables: [] extensions: [] extra_rdoc_files: - LICENSE.txt - README.rdoc files: - Gemfile - LICENSE.txt - README.rdoc - Rakefile - VERSION - lib/rack/cors.rb - rack-cors.gemspec - test/unit/cors_test.rb - test/unit/dsl_test.rb - test/unit/test.ru homepage: http://github.com/cyu/rack-cors licenses: - MIT post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' segments: - 0 hash: -4990705370164666 required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 1.8.24 signing_key: specification_version: 3 summary: Middleware for enabling Cross-Origin Resource Sharing in Rack apps test_files: [] ruby-rack-cors-0.2.7/rack-cors.gemspec000066400000000000000000000042741211637640100176240ustar00rootroot00000000000000# Generated by jeweler # DO NOT EDIT THIS FILE DIRECTLY # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' # -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = "rack-cors" s.version = "0.2.7" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Calvin Yu"] s.date = "2012-06-07" s.description = "Middleware that will make Rack-based apps CORS compatible. Read more here: http://blog.sourcebender.com/2010/06/09/introducin-rack-cors.html. Fork the project here: http://github.com/cyu/rack-cors" s.email = "me@sourcebender.com" s.extra_rdoc_files = [ "LICENSE.txt", "README.rdoc" ] s.files = [ "Gemfile", "LICENSE.txt", "README.rdoc", "Rakefile", "VERSION", "lib/rack/cors.rb", "rack-cors.gemspec", "test/unit/cors_test.rb", "test/unit/dsl_test.rb", "test/unit/test.ru" ] s.homepage = "http://github.com/cyu/rack-cors" s.licenses = ["MIT"] s.require_paths = ["lib"] s.rubygems_version = "1.8.24" s.summary = "Middleware for enabling Cross-Origin Resource Sharing in Rack apps" if s.respond_to? :specification_version then s.specification_version = 3 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_runtime_dependency(%q, [">= 0"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, ["~> 1.1.0"]) s.add_development_dependency(%q, ["~> 1.8.3"]) else s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["~> 1.1.0"]) s.add_dependency(%q, ["~> 1.8.3"]) end else s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["~> 1.1.0"]) s.add_dependency(%q, ["~> 1.8.3"]) end end ruby-rack-cors-0.2.7/test/000077500000000000000000000000001211637640100153435ustar00rootroot00000000000000ruby-rack-cors-0.2.7/test/unit/000077500000000000000000000000001211637640100163225ustar00rootroot00000000000000ruby-rack-cors-0.2.7/test/unit/cors_test.rb000066400000000000000000000117271211637640100206640ustar00rootroot00000000000000require 'rubygems' require 'rack/test' require 'shoulda' Rack::Test::Session.class_eval do def options(uri, params = {}, env = {}, &block) env = env_for(uri, env.merge(:method => "OPTIONS", :params => params)) process_request(uri, env, &block) end end Rack::Test::Methods.class_eval do def_delegator :current_session, :options end class CorsTest < Test::Unit::TestCase include Rack::Test::Methods def app eval "Rack::Builder.new {( " + File.read(File.dirname(__FILE__) + '/test.ru') + "\n )}" end should('support simple cors request') { cors_request } should 'support regex origins configuration' do cors_request :origin => 'http://192.168.0.1:1234' end should 'support alternative X-Origin header' do header 'X-Origin', 'http://localhost:3000' get '/' assert_cors_success end should 'support expose header configuration' do cors_request '/expose_single_header' assert_equal 'expose-test', last_response.headers['Access-Control-Expose-Headers'] end should 'support expose multiple header configuration' do cors_request '/expose_multiple_headers' assert_equal 'expose-test-1, expose-test-2', last_response.headers['Access-Control-Expose-Headers'] end should 'add Vary header if Access-Control-Allow-Origin header was added and if it is specific' do cors_request '/', :origin => "http://192.168.0.3:8080" assert_cors_success assert_equal 'http://192.168.0.3:8080', last_response.headers['Access-Control-Allow-Origin'] assert_not_nil last_response.headers['Vary'], 'missing Vary header' end should 'not add Vary header if Access-Control-Allow-Origin header was added and if it is generic (*)' do cors_request '/public_without_credentials', :origin => "http://192.168.1.3:8080" assert_cors_success assert_equal '*', last_response.headers['Access-Control-Allow-Origin'] assert_nil last_response.headers['Vary'], 'no expecting Vary header' end context 'preflight requests' do should 'fail if origin is invalid' do preflight_request('http://allyourdataarebelongtous.com', '/') assert_cors_failure end should 'fail if Access-Control-Request-Method does not exist' do preflight_request('http://localhost:3000', '/', :method => nil) assert_cors_failure end should 'fail if Access-Control-Request-Method is not allowed' do preflight_request('http://localhost:3000', '/get-only', :method => :post) assert_cors_failure end should 'fail if header is not allowed' do preflight_request('http://localhost:3000', '/single_header', :headers => 'Fooey') assert_cors_failure end should 'allow any header if headers = :any' do preflight_request('http://localhost:3000', '/', :headers => 'Fooey') assert_cors_success end should 'allow header case insensitive match' do preflight_request('http://localhost:3000', '/single_header', :headers => 'X-Domain-Token') assert_cors_success end should 'allow multiple headers match' do # Webkit style preflight_request('http://localhost:3000', '/two_headers', :headers => 'X-Requested-With, X-Domain-Token') assert_cors_success # Gecko style preflight_request('http://localhost:3000', '/two_headers', :headers => 'x-requested-with,x-domain-token') assert_cors_success end should '* origin should allow any origin' do preflight_request('http://locohost:3000', '/public') assert_cors_success assert_equal 'http://locohost:3000', last_response.headers['Access-Control-Allow-Origin'] end should '* origin should allow any origin, and set * if no credentials required' do preflight_request('http://locohost:3000', '/public_without_credentials') assert_cors_success assert_equal '*', last_response.headers['Access-Control-Allow-Origin'] end should 'return a Content-Type' do preflight_request('http://localhost:3000', '/') assert_cors_success assert_not_nil last_response.headers['Content-Type'] end end protected def cors_request(*args) path = args.first.is_a?(String) ? args.first : '/' opts = args.last.is_a?(Hash) ? args.last : {:origin => 'http://localhost:3000'} origin = opts[:origin] header 'Origin', origin get path assert_cors_success end def preflight_request(origin, path, opts = {}) header 'Origin', origin unless opts.key?(:method) && opts[:method].nil? header 'Access-Control-Request-Method', opts[:method] ? opts[:method].to_s.upcase : 'GET' end if opts[:headers] header 'Access-Control-Request-Headers', opts[:headers] end options path end def assert_cors_success assert_not_nil last_response.headers['Access-Control-Allow-Origin'], 'missing Access-Control-Allow-Origin header' end def assert_cors_failure assert_nil last_response.headers['Access-Control-Allow-Origin'], 'no expecting Access-Control-Allow-Origin header' end end ruby-rack-cors-0.2.7/test/unit/dsl_test.rb000066400000000000000000000017101211637640100204670ustar00rootroot00000000000000require 'rubygems' require 'rack/cors' require 'shoulda' class DSLTest < Test::Unit::TestCase should 'support explicit config object dsl mode' do cors = Rack::Cors.new(Proc.new {}) do |cfg| cfg.allow do |allow| allow.origins 'localhost:3000', '127.0.0.1:3000' allow.resource '/get-only', :methods => :get allow.resource '/', :headers => :any end end resources = cors.send :all_resources assert_equal 1, resources.length assert resources.first.allow_origin?('http://localhost:3000') end should 'support implicit config object dsl mode' do cors = Rack::Cors.new(Proc.new {}) do allow do origins 'localhost:3000', '127.0.0.1:3000' resource '/get-only', :methods => :get resource '/', :headers => :any end end resources = cors.send :all_resources assert_equal 1, resources.length assert resources.first.allow_origin?('http://localhost:3000') end endruby-rack-cors-0.2.7/test/unit/test.ru000066400000000000000000000015331211637640100176530ustar00rootroot00000000000000require 'rack/cors' use Rack::Cors do allow do origins 'localhost:3000', '127.0.0.1:3000', /http:\/\/192\.168\.0\.\d{1,3}(:\d+)?/ resource '/get-only', :methods => :get resource '/', :headers => :any resource '/single_header', :headers => 'x-domain-token' resource '/two_headers', :headers => %w{x-domain-token x-requested-with} resource '/expose_single_header', :expose => 'expose-test' resource '/expose_multiple_headers', :expose => %w{expose-test-1 expose-test-2} # resource '/file/at/*', # :methods => [:get, :post, :put, :delete], # :headers => :any, # :max_age => 0 end allow do origins '*' resource '/public' resource '/public_without_credentials', :credentials => false end end map '/' do run Proc.new { |env| [200, {'Content-Type' => 'text/html'}, ['success']] } end