cors 1.0.0

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.
@@ -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