cors 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ .yardoc/
2
+ doc/
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ -Ilib
3
+ -Ispec
4
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem "rake"
6
+ gem "yard"
@@ -0,0 +1,24 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ rake (0.9.2.2)
6
+ redcarpet (2.1.1)
7
+ rspec (2.11.0)
8
+ rspec-core (~> 2.11.0)
9
+ rspec-expectations (~> 2.11.0)
10
+ rspec-mocks (~> 2.11.0)
11
+ rspec-core (2.11.1)
12
+ rspec-expectations (2.11.3)
13
+ diff-lcs (~> 1.1.3)
14
+ rspec-mocks (2.11.3)
15
+ yard (0.8.2.1)
16
+
17
+ PLATFORMS
18
+ ruby
19
+
20
+ DEPENDENCIES
21
+ rake
22
+ redcarpet
23
+ rspec
24
+ yard
@@ -0,0 +1,67 @@
1
+ # CORS policy validation- and signing library
2
+
3
+ [![Build Status](https://secure.travis-ci.org/elabs/cors.png)](http://travis-ci.org/elabs/cors)
4
+
5
+ Cross-origin resource sharing (CORS) is great; it allows your visitors to asynchronously upload files to
6
+ e.g. Filepicker or Amazon S3, without the files having to round-trip through your web server. Unfortunately,
7
+ giving your users complete write access to your online storage also exposes you to malicious intent.
8
+
9
+ To combat harmful usage, good upload services that allow client-side upload, support a mechanism that allows
10
+ you to validate and sign all upload requests to your online storage. By validating every request, you can
11
+ give your visitors a nice upload experience, while keeping the bad visitors at bay.
12
+
13
+ ## Usage
14
+
15
+ ```ruby
16
+ UploadManifest = CORS::Policy::S3.create do |policy|
17
+ policy.required "method", "PUT"
18
+ policy.optional "md5" do |value|
19
+ Base64.strict_decode64(value)
20
+ end
21
+ policy.required "content-type", %r|image/|
22
+ policy.required "x-amz-date" do |date|
23
+ "2012-10-22T16:10:47+02:00" == date
24
+ end
25
+ policy.required "filename", %r|uploads/|
26
+ end
27
+
28
+ manifest = UploadManifest.new(params)
29
+
30
+ response = if manifest.valid?
31
+ { success: manifest.sign(access_key, secret_access_key) }
32
+ else
33
+ { error: manifest.errors }
34
+ end
35
+ ```
36
+
37
+ ## Supported services
38
+
39
+ Out-of-the box, the CORS library comes with support for the Amazon S3 REST API. Support
40
+ for Filepicker is planned.
41
+
42
+ - [Amazon S3 REST API](http://docs.amazonwebservices.com/AmazonS4/latest/dev/RESTAuthentication.html)
43
+
44
+ ## License
45
+
46
+ Copyright (c) 2012 Kim Burgestrand
47
+
48
+ MIT License
49
+
50
+ Permission is hereby granted, free of charge, to any person obtaining
51
+ a copy of this software and associated documentation files (the
52
+ "Software"), to deal in the Software without restriction, including
53
+ without limitation the rights to use, copy, modify, merge, publish,
54
+ distribute, sublicense, and/or sell copies of the Software, and to
55
+ permit persons to whom the Software is furnished to do so, subject to
56
+ the following conditions:
57
+
58
+ The above copyright notice and this permission notice shall be
59
+ included in all copies or substantial portions of the Software.
60
+
61
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
62
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
63
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
64
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
65
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
66
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
67
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,33 @@
1
+ begin
2
+ require "bundler/gem_tasks"
3
+ rescue LoadError
4
+ end
5
+
6
+ begin
7
+ require 'yard'
8
+ YARD::Rake::YardocTask.new('yard:doc') do |task|
9
+ task.options = ['--no-stats']
10
+ end
11
+
12
+ desc "Run documentation statistics"
13
+ task 'yard:stats' do
14
+ YARD::CLI::Stats.run('--list-undoc')
15
+ end
16
+
17
+ desc "Generate documentation and run documentation statistics"
18
+ task :yard => ['yard:doc', 'yard:stats']
19
+ rescue LoadError
20
+ puts "WARN: YARD not available. You may install documentation dependencies via bundler."
21
+ end
22
+
23
+ require "rspec/core/rake_task"
24
+ RSpec::Core::RakeTask.new do |spec|
25
+ spec.ruby_opts = "-W"
26
+ end
27
+
28
+ desc "Launch a console with the library loaded"
29
+ task :console do
30
+ exec "irb", "-Ilib", "-rcors"
31
+ end
32
+
33
+ task :default => :spec
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cors/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "cors"
8
+ gem.version = CORS::VERSION
9
+ gem.authors = ["Kim Burgestrand"]
10
+ gem.email = ["kim@burgestrand.se"]
11
+ gem.homepage = "http://github.com/elabs/cors"
12
+ gem.summary = "CORS policy validation- and signing library for Amazon S3 REST API."
13
+ gem.description = <<-DESCRIPTION.gsub(/ +/, "")
14
+ Cross-origin resource sharing (CORS) is great; it allows your visitors to
15
+ asynchronously upload files to e.g. Filepicker or Amazon S3, without the
16
+ files having to round-trip through your web server. Unfortunately, giving
17
+ your users complete write access to your online storage also exposes you to
18
+ malicious intent.
19
+
20
+ To combat harmful usage, good upload services that allow client-side
21
+ upload, support a mechanism that allows you to validate and sign all upload
22
+ requests to your online storage. By validating every request, you can give
23
+ your visitors a nice upload experience, while keeping the bad visitors at
24
+ bay.
25
+
26
+ The CORS gem comes with support for the Amazon S3 REST API.
27
+ DESCRIPTION
28
+
29
+ gem.files = `git ls-files`.split($/)
30
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
31
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
32
+ gem.require_paths = ["lib"]
33
+
34
+ gem.add_development_dependency "rspec", "~> 2.0"
35
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+ require "cors/version"
3
+ require "cors/rules"
4
+ require "cors/policy"
5
+ require "cors/policy/s3"
6
+
7
+ # CORS policy validation and signature generation.
8
+ #
9
+ # @example usage for S3 REST API authorization header
10
+ # UploadManifest = CORS::Policy::S3.create do |policy|
11
+ # policy.required "method", "PUT"
12
+ # policy.optional "md5" do |value|
13
+ # Base64.strict_decode64(value)
14
+ # end
15
+ # policy.required "content-type", %r|image/|
16
+ # policy.required "x-amz-date" do |date|
17
+ # "2012-10-22T16:10:47+02:00" == date
18
+ # end
19
+ # policy.required "filename", %r|uploads/|
20
+ # end
21
+ #
22
+ # manifest = UploadManifest.new(params)
23
+ #
24
+ # response = if manifest.valid?
25
+ # { success: manifest.sign(access_key, secret_access_key) }
26
+ # else
27
+ # { error: manifest.errors }
28
+ # end
29
+ #
30
+ # @see CORS::Policy
31
+ module CORS
32
+ end
@@ -0,0 +1,109 @@
1
+ module CORS
2
+ # Mixin for declaring CORS Policies.
3
+ #
4
+ # Classes who include this mixin should define both #manifest and #sign.
5
+ #
6
+ # @example
7
+ # class S3
8
+ # include CORS::Policy
9
+ #
10
+ # def manifest
11
+ # # create the manifest
12
+ # [].tap do |manifest|
13
+ # manifest << attributes["method"].upcase
14
+ # end.join("\n")
15
+ # end
16
+ #
17
+ # def sign(access_key, secret_access_key)
18
+ # # sign the manifest
19
+ # end
20
+ # end
21
+ #
22
+ # policy = S3.create do |rules|
23
+ # rules.required "method", %w[GET]
24
+ # end
25
+ #
26
+ # @see CORS::Rules
27
+ module Policy
28
+ # Class methods added to includers of {CORS::Policy}.
29
+ #
30
+ # @see {CORS::Policy}
31
+ module ClassMethods
32
+ # @return [CORS::Rules]
33
+ attr_reader :rules
34
+
35
+ # Create an instance of this policy, declaring rules as well.
36
+ #
37
+ # @example
38
+ # upload_policy = CORS::Policy::S3.create do |rules|
39
+ # rules.required "method", %w[GET]
40
+ # end
41
+ #
42
+ # @raise [ArgumentError] if no block is supplied
43
+ # @yield [rules] allows you to declare rules on the newly created policy
44
+ # @yieldparam [CORS::Rules] rules
45
+ def create(*, &block)
46
+ unless block_given?
47
+ raise ArgumentError, "manifest rules must be specified by a block, no block given"
48
+ end
49
+
50
+ Class.new(self) do
51
+ @rules = CORS::Rules.new(&block)
52
+ end
53
+ end
54
+ end
55
+
56
+ class << self
57
+ # Extends the target with {ClassMethods}
58
+ #
59
+ # @param [#extend] other
60
+ def included(other)
61
+ other.extend(ClassMethods)
62
+ end
63
+ end
64
+
65
+ # Initialize the policy with the given attributes and validate the attributes.
66
+ #
67
+ # @note attribute keys are converted to strings and downcased for validation
68
+ # @note validations are run instantly
69
+ #
70
+ # @param [Hash] attributes
71
+ # @see errors
72
+ # @see valid?
73
+ def initialize(attributes)
74
+ self.attributes = Hash[attributes.map { |k, v| [k.to_s.downcase, v] }]
75
+ self.errors = rules.validate(self.attributes)
76
+ end
77
+
78
+ # @return [Hash<String, Object>]
79
+ attr_accessor :attributes
80
+ protected :attributes=
81
+
82
+ # @return [Hash]
83
+ attr_accessor :errors
84
+ protected :errors=
85
+
86
+ # @raise [RuntimeError] raises if no rules have been defined
87
+ # @return [CORS::Rules] rules assigned to this policy
88
+ def rules
89
+ self.class.rules or raise "no rules defined for policy #{inspect}"
90
+ end
91
+
92
+ # @return [Boolean] true if no errors was encountered during validation in {#initialize}
93
+ def valid?
94
+ errors.empty?
95
+ end
96
+
97
+ # @note must be overridden by includers!
98
+ # @return [String] the compiled manifest
99
+ def manifest
100
+ raise NotImplementedError, "#manifest has not been defined on #{inspect}"
101
+ end
102
+
103
+ # @note must be overridden by includers!
104
+ # @return [String] signature derived from the manifest
105
+ def sign(*)
106
+ raise NotImplementedError, "#sign has not been defined on #{inspect}"
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,47 @@
1
+ require "openssl"
2
+ require "base64"
3
+
4
+ module CORS::Policy
5
+ # CORS policy for Amazon S3. See {CORS} module documenation for an example.
6
+ #
7
+ # @see CORS
8
+ class S3
9
+ include CORS::Policy
10
+
11
+ # Compile the S3 authorization manifest from the parameters.
12
+ #
13
+ # @see http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html#ConstructingTheAuthenticationHeader
14
+ def manifest
15
+ [].tap do |manifest|
16
+ manifest << attributes["method"].upcase
17
+ manifest << attributes["md5"]
18
+ manifest << attributes["content-type"]
19
+ manifest << attributes["date"]
20
+ normalized_headers.each do |(header, *values)|
21
+ manifest << "#{header}:#{values.join(",")}"
22
+ end
23
+ manifest << attributes["filename"]
24
+ end.join("\n")
25
+ end
26
+
27
+ # Sign the {#manifest} with the AWS credentials.
28
+ #
29
+ # @param [String] access_key
30
+ # @param [String] secret_access_key
31
+ def sign(access_key, secret_access_key)
32
+ return if not valid?
33
+ digest = OpenSSL::HMAC.digest("sha1", secret_access_key, manifest)
34
+ signature = Base64.strict_encode64(digest)
35
+ "AWS #{access_key}:#{signature}"
36
+ end
37
+
38
+ protected
39
+
40
+ # @return [Array] list of aws-specific headers properly sorted
41
+ def normalized_headers
42
+ attributes.select { |property, _| property =~ /x-amz-/ }
43
+ .map { |(header, values)| [header.downcase, values] }
44
+ .sort_by { |(header, _)| header }
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,121 @@
1
+ module CORS
2
+ # Internal class for handling rule definitions and validation.
3
+ #
4
+ # @private
5
+ class Rules
6
+ include Enumerable
7
+
8
+ # @example
9
+ # Rules.new do |rules|
10
+ # rules.required …
11
+ # rules.optional …
12
+ # end
13
+ #
14
+ # @yield [self]
15
+ # @yieldparam [Rules] self
16
+ def initialize
17
+ @rules = []
18
+ yield self if block_given?
19
+ end
20
+
21
+ # Yields each rule in order, or returns an Enumerator
22
+ # if no block was given.
23
+ #
24
+ # @example
25
+ # rules.each do |rule|
26
+ # …
27
+ # end
28
+ #
29
+ # @example without block
30
+ # rules.each.with_index do |rule, index|
31
+ # …
32
+ # end
33
+ #
34
+ # @return [Hash<:name, :matcher, :required>, Enumerator]
35
+ def each
36
+ if block_given?
37
+ @rules.each { |rule| yield rule }
38
+ else
39
+ @rules.enum_for(__method__)
40
+ end
41
+ end
42
+
43
+ # Declare a required rule; the value must be present, and it must
44
+ # match the given constraints or block matcher.
45
+ #
46
+ # @example with a regexp
47
+ # @required "content-type", %r|image/jpe?g|
48
+ #
49
+ # @example with a string
50
+ # required "content-type", "image/jpeg"
51
+ #
52
+ # @example with an array
53
+ # required "content-type", ["image/jpeg", "image/jpg"]
54
+ #
55
+ # @example with a block
56
+ # required "content-type" do |value|
57
+ # value =~ %r|image/jpe?g|
58
+ # end
59
+ #
60
+ # @param name can be any valid hash key of the parameters to be validated
61
+ # @param [Regexp, String, Array] constraints
62
+ # @yield [value]
63
+ # @yieldparam value of the key `name` in the parameters to be validated
64
+ # @return [Hash] the newly created rule
65
+ def required(name, constraints = nil, &block)
66
+ matcher = if block_given? then block
67
+ elsif constraints.is_a?(Regexp)
68
+ constraints.method(:===)
69
+ elsif constraints.is_a?(String)
70
+ constraints.method(:===)
71
+ elsif constraints.is_a?(Array)
72
+ constraints.method(:include?)
73
+ else
74
+ raise ArgumentError, "unknown matcher #{(constraints || block).inspect}"
75
+ end
76
+
77
+ { name: name, matcher: matcher, required: true }.tap do |rule|
78
+ @rules << rule
79
+ end
80
+ end
81
+
82
+ # Same as {#required}, but the rule won’t run if the key is not present.
83
+ #
84
+ # @param (see required)
85
+ # @return (see required)
86
+ # @see required
87
+ def optional(*args, &block)
88
+ required(*args, &block).tap { |rule| rule[:required] = false }
89
+ end
90
+
91
+ # Validate a set of attributes against the defined rules.
92
+ #
93
+ # @example
94
+ # errors = rules.validate(params)
95
+ # if errors.empty?
96
+ # # valid
97
+ # else
98
+ # # not valid, errors is a hash of { name => [ reason, rule ] }
99
+ # end
100
+ #
101
+ # @see required
102
+ # @param [#has_key?, #[]] attributes
103
+ # @return [Hash<name: [reason, rule]>] list of errors, empty if attributes are valid
104
+ def validate(attributes)
105
+ each_with_object({}) do |rule, failures|
106
+ fail = lambda do |reason|
107
+ failures[rule[:name]] = [reason, rule]
108
+ end
109
+
110
+ unless attributes.has_key?(rule[:name])
111
+ fail[:required] if rule[:required]
112
+ next
113
+ end
114
+
115
+ unless rule[:matcher].call(attributes[rule[:name]])
116
+ fail[:match]
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,4 @@
1
+ module CORS
2
+ # @see http://semver.org/
3
+ VERSION = "1.0.0"
4
+ end
@@ -0,0 +1,60 @@
1
+ describe CORS::Policy do
2
+ let(:policy) do
3
+ Class.new { include CORS::Policy }
4
+ end
5
+
6
+ let(:valid_attributes) do
7
+ {
8
+ "anything" => "Yay!"
9
+ }
10
+ end
11
+
12
+ let(:manifest) do
13
+ policy.create do |rules|
14
+ rules.required "anything", //
15
+ end
16
+ end
17
+
18
+ describe ".initialize" do
19
+ it "requires a block" do
20
+ expect { CORS::Policy::S3.create }.to raise_error(ArgumentError, /no block given/)
21
+ end
22
+ end
23
+
24
+ describe "#initialize" do
25
+ it "requires attributes" do
26
+ expect { manifest.new }.to raise_error(ArgumentError, /wrong number of arguments/)
27
+ end
28
+
29
+ it "normalizes the attribute keys" do
30
+ manifest.new(cOOl: :Yo).attributes.should eq({ "cool" => :Yo })
31
+ end
32
+
33
+ it "populates the hash of errors" do
34
+ manifest.new({}).errors.should_not be_empty
35
+ end
36
+ end
37
+
38
+ describe "#rules" do
39
+ it "raises an error if no rules have been defined" do
40
+ expect { policy.new({}) }.to raise_error(/no rules defined/)
41
+ end
42
+
43
+ it "returns the raw rules" do
44
+ manifest.rules.should be_a CORS::Rules
45
+ end
46
+ end
47
+
48
+ describe "#valid?" do
49
+ it "returns true if validation succeeds" do
50
+ manifest.new(valid_attributes).tap do |manifest|
51
+ manifest.should be_valid
52
+ manifest.errors.should eq({})
53
+ end
54
+ end
55
+
56
+ it "returns false if validation fails" do
57
+ manifest.new({}).should_not be_valid
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,59 @@
1
+ describe CORS::Policy::S3 do
2
+ let(:valid_attributes) do
3
+ {
4
+ "method" => "PUT",
5
+ "md5" => "CCummMp6o4ZgypU7ePh7QA==",
6
+ "content-type" => "image/jpeg",
7
+ "x-amz-meta-filename" => "roflcopter.gif",
8
+ "x-amz-date" => "2012-10-22T16:10:47+02:00",
9
+ "x-amz-meta-ROFLCOPTER" => ["yes", "no", "maybe"],
10
+ "x-not-amz-header" => "I am ignored",
11
+ "filename" => "uploads/roflcopter.gif"
12
+ }
13
+ end
14
+
15
+ let(:rules) do
16
+ lambda do |manifest|
17
+ manifest.required "method", "PUT"
18
+ manifest.optional "md5" do |value|
19
+ Base64.strict_decode64(value)
20
+ end
21
+ manifest.required "content-type", %r|image/|
22
+ manifest.required "x-amz-date" do |date|
23
+ "2012-10-22T16:10:47+02:00" == date
24
+ end
25
+ manifest.required "filename", %r|uploads/|
26
+ end
27
+ end
28
+
29
+ let(:manifest) { CORS::Policy::S3.create(&rules) }
30
+
31
+ describe "#manifest" do
32
+ it "is built according to specifications" do
33
+ manifest = CORS::Policy::S3.create(&rules).new(valid_attributes)
34
+ manifest.manifest.should eq <<-MANIFEST.gsub(/^ +/, "").rstrip
35
+ PUT
36
+ CCummMp6o4ZgypU7ePh7QA==
37
+ image/jpeg
38
+
39
+ x-amz-date:2012-10-22T16:10:47+02:00
40
+ x-amz-meta-filename:roflcopter.gif
41
+ x-amz-meta-roflcopter:yes,no,maybe
42
+ uploads/roflcopter.gif
43
+ MANIFEST
44
+ end
45
+ end
46
+
47
+ describe "#sign" do
48
+ it "signs the manifest if it is valid" do
49
+ manifest = CORS::Policy::S3.create(&rules).new(valid_attributes)
50
+ manifest.sign("LAWL", "HELLO").should eq "AWS LAWL:WZGsk2VzLz85B6oU19a5+fvzxXM="
51
+ end
52
+
53
+ it "does not sign if the manifest is invalid" do
54
+ manifest = CORS::Policy::S3.create(&rules).new(valid_attributes)
55
+ manifest.should_receive(:valid?).and_return(false)
56
+ manifest.sign("LAWL", "HELLO").should be_nil
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,89 @@
1
+ describe CORS::Rules do
2
+ describe "#each" do
3
+ let(:list) { [] }
4
+ let(:rules) do
5
+ CORS::Rules.new do |r|
6
+ list << r.required("yay", //)
7
+ list << r.optional("boo", //)
8
+ end
9
+ end
10
+
11
+ it "is enumerable" do
12
+ rules.each_with_object([]) { |rule, result| result << rule }.should eq(list)
13
+ end
14
+
15
+ it "returns an enumerator without a block" do
16
+ rules.each.with_object([]) { |rule, result| result << rule }.should eq(list)
17
+ end
18
+ end
19
+
20
+ describe "#required" do
21
+ it "does not accept arbitrary constraints" do
22
+ expect { CORS::Rules.new { |r| r.required "method", false } }.to raise_error(ArgumentError, /unknown matcher/)
23
+ end
24
+
25
+ it "results in an error when the value is missing" do
26
+ rules = CORS::Rules.new { |r| r.required "method", // }
27
+ errors = rules.validate({})
28
+
29
+ errors.should eq({ "method" => [:required, rules.first] })
30
+ end
31
+
32
+ it "results in an error when the value does not match" do
33
+ rules = CORS::Rules.new { |r| r.required "content-type", %r|image/jpe?g| }
34
+ errors = rules.validate({ "content-type" => "image/png" })
35
+
36
+ errors.should eq({ "content-type" => [:match, rules.first] })
37
+ end
38
+
39
+ it "can match a regexp" do
40
+ rules = CORS::Rules.new { |r| r.required "content-type", %r|image/jpe?g| }
41
+
42
+ rules.validate({ "content-type" => "image/jpeg" }).should be_empty
43
+ rules.validate({ "content-type" => "image/jpg" }).should be_empty
44
+ rules.validate({ "content-type" => "image/png" }).should_not be_empty
45
+ end
46
+
47
+ it "can match a literal string" do
48
+ rules = CORS::Rules.new { |r| r.required "content-type", "image/jpeg" }
49
+
50
+ rules.validate({ "content-type" => "image/jpeg" }).should be_empty
51
+ rules.validate({ "content-type" => "image/jpg" }).should_not be_empty
52
+ end
53
+
54
+ it "can match an array" do
55
+ rules = CORS::Rules.new { |r| r.required "content-type", ["image/jpeg", "image/png"] }
56
+
57
+ rules.validate({ "content-type" => "image/jpeg" }).should be_empty
58
+ rules.validate({ "content-type" => "image/png" }).should be_empty
59
+ rules.validate({ "content-type" => "image/jpg" }).should_not be_empty
60
+ end
61
+
62
+ it "can match a block" do
63
+ rules = CORS::Rules.new do |r|
64
+ r.required "content-type" do |type|
65
+ "image/jpeg" == type
66
+ end
67
+ end
68
+
69
+ rules.validate({ "content-type" => "image/jpeg" }).should be_empty
70
+ rules.validate({ "content-type" => "image/png" }).should_not be_empty
71
+ end
72
+ end
73
+
74
+ describe "#optional" do
75
+ it "results in no error when the value is missing" do
76
+ rules = CORS::Rules.new { |r| r.optional "method", // }
77
+ errors = rules.validate({})
78
+
79
+ errors.should be_empty
80
+ end
81
+
82
+ it "results in an error when the value is present but does not match" do
83
+ rules = CORS::Rules.new { |r| r.optional "content-type", %r|image/jpe?g| }
84
+ errors = rules.validate({ "content-type" => "image/png" })
85
+
86
+ errors.should eq({ "content-type" => [:match, rules.first] })
87
+ end
88
+ end
89
+ end
@@ -0,0 +1 @@
1
+ require "cors"
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cors
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kim Burgestrand
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '2.0'
30
+ description: ! 'Cross-originresourcesharing(CORS)isgreat;itallowsyourvisitorsto
31
+
32
+ asynchronouslyuploadfilestoe.g.FilepickerorAmazonS3,withoutthe
33
+
34
+ fileshavingtoround-tripthroughyourwebserver.Unfortunately,giving
35
+
36
+ youruserscompletewriteaccesstoyouronlinestoragealsoexposesyouto
37
+
38
+ maliciousintent.
39
+
40
+
41
+ Tocombatharmfulusage,gooduploadservicesthatallowclient-side
42
+
43
+ upload,supportamechanismthatallowsyoutovalidateandsignallupload
44
+
45
+ requeststoyouronlinestorage.Byvalidatingeveryrequest,youcangive
46
+
47
+ yourvisitorsaniceuploadexperience,whilekeepingthebadvisitorsat
48
+
49
+ bay.
50
+
51
+
52
+ TheCORSgemcomeswithsupportfortheAmazonS3RESTAPI.
53
+
54
+ '
55
+ email:
56
+ - kim@burgestrand.se
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - .gitignore
62
+ - .rspec
63
+ - Gemfile
64
+ - Gemfile.lock
65
+ - README.md
66
+ - Rakefile
67
+ - cors.gemspec
68
+ - lib/cors.rb
69
+ - lib/cors/policy.rb
70
+ - lib/cors/policy/s3.rb
71
+ - lib/cors/rules.rb
72
+ - lib/cors/version.rb
73
+ - spec/cors_spec.rb
74
+ - spec/policies/s3_spec.rb
75
+ - spec/rules_spec.rb
76
+ - spec/spec_helper.rb
77
+ homepage: http://github.com/elabs/cors
78
+ licenses: []
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 1.8.24
98
+ signing_key:
99
+ specification_version: 3
100
+ summary: CORS policy validation- and signing library for Amazon S3 REST API.
101
+ test_files:
102
+ - spec/cors_spec.rb
103
+ - spec/policies/s3_spec.rb
104
+ - spec/rules_spec.rb
105
+ - spec/spec_helper.rb