rack-protection 1.0.0 → 1.5.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,23 +2,74 @@
2
2
  Gem::Specification.new do |s|
3
3
  # general infos
4
4
  s.name = "rack-protection"
5
- s.version = "1.0.0"
5
+ s.version = "1.5.5"
6
6
  s.description = "You should use protection!"
7
7
  s.homepage = "http://github.com/rkh/rack-protection"
8
8
  s.summary = s.description
9
+ s.license = 'MIT'
9
10
 
10
11
  # generated from git shortlog -sn
11
12
  s.authors = [
12
13
  "Konstantin Haase",
14
+ "Alex Rodionov",
15
+ "Patrick Ellis",
16
+ "Jason Staten",
17
+ "ITO Nobuaki",
18
+ "Jeff Welling",
19
+ "Matteo Centenaro",
20
+ "Egor Homakov",
21
+ "Florian Gilcher",
22
+ "Fojas",
23
+ "Igor Bochkariov",
24
+ "Mael Clerambault",
25
+ "Martin Mauch",
26
+ "Renne Nissinen",
27
+ "SAKAI, Kazuaki",
28
+ "Stanislav Savulchik",
29
+ "Steve Agalloco",
30
+ "TOBY",
31
+ "Thais Camilo and Konstantin Haase",
32
+ "Vipul A M",
33
+ "Akzhan Abdulin",
34
+ "brookemckim",
35
+ "Bj\u{f8}rge N\u{e6}ss",
36
+ "Chris Heald",
37
+ "Chris Mytton",
13
38
  "Corey Ward",
14
- "Fojas"
39
+ "Dario Cravero",
40
+ "David Kellum"
15
41
  ]
16
42
 
17
43
  # generated from git shortlog -sne
18
44
  s.email = [
19
45
  "konstantin.mailinglists@googlemail.com",
46
+ "p0deje@gmail.com",
47
+ "jstaten07@gmail.com",
48
+ "patrick@soundcloud.com",
49
+ "jeff.welling@gmail.com",
50
+ "bugant@gmail.com",
51
+ "daydream.trippers@gmail.com",
52
+ "florian.gilcher@asquera.de",
53
+ "developer@fojasaur.us",
54
+ "ujifgc@gmail.com",
55
+ "mael@clerambault.fr",
56
+ "martin.mauch@gmail.com",
57
+ "rennex@iki.fi",
58
+ "kaz.july.7@gmail.com",
59
+ "s.savulchik@gmail.com",
60
+ "steve.agalloco@gmail.com",
61
+ "toby.net.info.mail+git@gmail.com",
62
+ "dev+narwen+rkh@rkh.im",
63
+ "vipulnsward@gmail.com",
64
+ "akzhan.abdulin@gmail.com",
65
+ "brooke@digitalocean.com",
66
+ "bjoerge@bengler.no",
67
+ "cheald@gmail.com",
68
+ "self@hecticjeff.net",
20
69
  "coreyward@me.com",
21
- "developer@fojasaur.us"
70
+ "dario@uxtemple.com",
71
+ "dek-oss@gravitext.com",
72
+ "homakov@gmail.com"
22
73
  ]
23
74
 
24
75
  # generated from git ls-files
@@ -33,6 +84,7 @@ Gem::Specification.new do |s|
33
84
  "lib/rack/protection/escaped_params.rb",
34
85
  "lib/rack/protection/form_token.rb",
35
86
  "lib/rack/protection/frame_options.rb",
87
+ "lib/rack/protection/http_origin.rb",
36
88
  "lib/rack/protection/ip_spoofing.rb",
37
89
  "lib/rack/protection/json_csrf.rb",
38
90
  "lib/rack/protection/path_traversal.rb",
@@ -43,9 +95,11 @@ Gem::Specification.new do |s|
43
95
  "lib/rack/protection/xss_header.rb",
44
96
  "rack-protection.gemspec",
45
97
  "spec/authenticity_token_spec.rb",
98
+ "spec/base_spec.rb",
46
99
  "spec/escaped_params_spec.rb",
47
100
  "spec/form_token_spec.rb",
48
101
  "spec/frame_options_spec.rb",
102
+ "spec/http_origin_spec.rb",
49
103
  "spec/ip_spoofing_spec.rb",
50
104
  "spec/json_csrf_spec.rb",
51
105
  "spec/path_traversal_spec.rb",
@@ -59,7 +113,6 @@ Gem::Specification.new do |s|
59
113
 
60
114
  # dependencies
61
115
  s.add_dependency "rack"
62
- s.add_dependency "escape_utils"
63
116
  s.add_development_dependency "rack-test"
64
117
  s.add_development_dependency "rspec", "~> 2.0"
65
118
  end
@@ -30,4 +30,19 @@ describe Rack::Protection::AuthenticityToken do
30
30
  it "prevents ajax requests without a valid token" do
31
31
  post('/', {}, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest").should_not be_ok
32
32
  end
33
+
34
+ it "allows for a custom authenticity token param" do
35
+ mock_app do
36
+ use Rack::Protection::AuthenticityToken, :authenticity_param => 'csrf_param'
37
+ run proc { |e| [200, {'Content-Type' => 'text/plain'}, ['hi']] }
38
+ end
39
+
40
+ post('/', {"csrf_param" => "a"}, 'rack.session' => {:csrf => "a"})
41
+ last_response.should be_ok
42
+ end
43
+
44
+ it "sets a new csrf token for the session in env, even after a 'safe' request" do
45
+ get('/', {}, {})
46
+ env['rack.session'][:csrf].should_not be_nil
47
+ end
33
48
  end
data/spec/base_spec.rb ADDED
@@ -0,0 +1,40 @@
1
+ require File.expand_path('../spec_helper.rb', __FILE__)
2
+
3
+ describe Rack::Protection::Base do
4
+
5
+ subject { described_class.new(lambda {}) }
6
+
7
+ describe "#random_string" do
8
+ it "outputs a string of 32 characters" do
9
+ subject.random_string.length.should == 32
10
+ end
11
+ end
12
+
13
+ describe "#referrer" do
14
+ it "Reads referrer from Referer header" do
15
+ env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "http://bar.com/valid"}
16
+ subject.referrer(env).should == "bar.com"
17
+ end
18
+
19
+ it "Reads referrer from Host header when Referer header is relative" do
20
+ env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "/valid"}
21
+ subject.referrer(env).should == "foo.com"
22
+ end
23
+
24
+ it "Reads referrer from Host header when Referer header is missing" do
25
+ env = {"HTTP_HOST" => "foo.com"}
26
+ subject.referrer(env).should == "foo.com"
27
+ end
28
+
29
+ it "Returns nil when Referer header is missing and allow_empty_referrer is false" do
30
+ env = {"HTTP_HOST" => "foo.com"}
31
+ subject.options[:allow_empty_referrer] = false
32
+ subject.referrer(env).should be_nil
33
+ end
34
+
35
+ it "Returns nil when Referer header is invalid" do
36
+ env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "http://bar.com/bad|uri"}
37
+ subject.referrer(env).should be_nil
38
+ end
39
+ end
40
+ end
@@ -30,5 +30,14 @@ describe Rack::Protection::EscapedParams do
30
30
  get '/', :foo => {:bar => "<bar>"}
31
31
  body.should == '&lt;bar&gt;'
32
32
  end
33
+
34
+ it 'leaves cache-breaker params untouched' do
35
+ mock_app do |env|
36
+ [200, {'Content-Type' => 'text/plain'}, ['hi']]
37
+ end
38
+
39
+ get '/?95df8d9bf5237ad08df3115ee74dcb10'
40
+ body.should == 'hi'
41
+ end
33
42
  end
34
43
  end
@@ -3,8 +3,12 @@ require File.expand_path('../spec_helper.rb', __FILE__)
3
3
  describe Rack::Protection::FrameOptions do
4
4
  it_behaves_like "any rack application"
5
5
 
6
- it 'should set the X-XSS-Protection' do
7
- get('/').headers["X-Frame-Options"].should == "sameorigin"
6
+ it 'should set the X-Frame-Options' do
7
+ get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"].should == "SAMEORIGIN"
8
+ end
9
+
10
+ it 'should not set the X-Frame-Options for other content types' do
11
+ get('/', {}, 'wants' => 'text/foo').headers["X-Frame-Options"].should be_nil
8
12
  end
9
13
 
10
14
  it 'should allow changing the protection mode' do
@@ -14,11 +18,22 @@ describe Rack::Protection::FrameOptions do
14
18
  run DummyApp
15
19
  end
16
20
 
17
- get('/').headers["X-Frame-Options"].should == "deny"
21
+ get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"].should == "DENY"
22
+ end
23
+
24
+
25
+ it 'should allow changing the protection mode to a string' do
26
+ # I have no clue what other modes are available
27
+ mock_app do
28
+ use Rack::Protection::FrameOptions, :frame_options => "ALLOW-FROM foo"
29
+ run DummyApp
30
+ end
31
+
32
+ get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"].should == "ALLOW-FROM foo"
18
33
  end
19
34
 
20
35
  it 'should not override the header if already set' do
21
36
  mock_app with_headers("X-Frame-Options" => "allow")
22
- get('/').headers["X-Frame-Options"].should == "allow"
37
+ get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"].should == "allow"
23
38
  end
24
39
  end
@@ -0,0 +1,38 @@
1
+ require File.expand_path('../spec_helper.rb', __FILE__)
2
+
3
+ describe Rack::Protection::HttpOrigin do
4
+ it_behaves_like "any rack application"
5
+
6
+ before(:each) do
7
+ mock_app do
8
+ use Rack::Protection::HttpOrigin
9
+ run DummyApp
10
+ end
11
+ end
12
+
13
+ %w(GET HEAD POST PUT DELETE).each do |method|
14
+ it "accepts #{method} requests with no Origin" do
15
+ send(method.downcase, '/').should be_ok
16
+ end
17
+ end
18
+
19
+ %w(GET HEAD).each do |method|
20
+ it "accepts #{method} requests with non-whitelisted Origin" do
21
+ send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://malicious.com').should be_ok
22
+ end
23
+ end
24
+
25
+ %w(POST PUT DELETE).each do |method|
26
+ it "denies #{method} requests with non-whitelisted Origin" do
27
+ send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://malicious.com').should_not be_ok
28
+ end
29
+
30
+ it "accepts #{method} requests with whitelisted Origin" do
31
+ mock_app do
32
+ use Rack::Protection::HttpOrigin, :origin_whitelist => ['http://www.friend.com']
33
+ run DummyApp
34
+ end
35
+ send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://www.friend.com').should be_ok
36
+ end
37
+ end
38
+ end
@@ -12,6 +12,14 @@ describe Rack::Protection::JsonCsrf do
12
12
  get('/', {}, 'HTTP_REFERER' => 'http://evil.com').should_not be_ok
13
13
  end
14
14
 
15
+ it "accepts requests with json responses with a remote referrer when there's an origin header set" do
16
+ get('/', {}, 'HTTP_REFERER' => 'http://good.com', 'HTTP_ORIGIN' => 'http://good.com').should be_ok
17
+ end
18
+
19
+ it "accepts requests with json responses with a remote referrer when there's an x-origin header set" do
20
+ get('/', {}, 'HTTP_REFERER' => 'http://good.com', 'HTTP_X_ORIGIN' => 'http://good.com').should be_ok
21
+ end
22
+
15
23
  it "accepts get requests with json responses with a local referrer" do
16
24
  get('/', {}, 'HTTP_REFERER' => '/').should be_ok
17
25
  end
@@ -19,6 +27,11 @@ describe Rack::Protection::JsonCsrf do
19
27
  it "accepts get requests with json responses with no referrer" do
20
28
  get('/', {}).should be_ok
21
29
  end
30
+
31
+ it "accepts XHR requests" do
32
+ get('/', {}, 'HTTP_REFERER' => 'http://evil.com', 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest').should be_ok
33
+ end
34
+
22
35
  end
23
36
 
24
37
  describe 'not json response' do
@@ -29,4 +42,17 @@ describe Rack::Protection::JsonCsrf do
29
42
  end
30
43
 
31
44
  end
45
+
46
+ describe 'with drop_session as default reaction' do
47
+ it 'still denies' do
48
+ mock_app do
49
+ use Rack::Protection, :reaction => :drop_session
50
+ run proc { |e| [200, {'Content-Type' => 'application/json'}, []]}
51
+ end
52
+
53
+ session = {:foo => :bar}
54
+ get('/', {}, 'HTTP_REFERER' => 'http://evil.com', 'rack.session' => session)
55
+ last_response.should_not be_ok
56
+ end
57
+ end
32
58
  end
@@ -14,10 +14,28 @@ describe Rack::Protection::PathTraversal do
14
14
 
15
15
  { # yes, this is ugly, feel free to change that
16
16
  '/..' => '/', '/a/../b' => '/b', '/a/../b/' => '/b/', '/a/.' => '/a/',
17
- '/%2e.' => '/', '/a/%2e%2e/b' => '/b', '/a%2f%2e%2e%2fb/' => '/b/',
18
- '//' => '/', '/%2fetc%2fpasswd' => '/etc/passwd'
17
+ '/%2e.' => '/', '/a/%2E%2e/b' => '/b', '/a%2f%2E%2e%2Fb/' => '/b/',
18
+ '//' => '/', '/%2fetc%2Fpasswd' => '/etc/passwd'
19
19
  }.each do |a, b|
20
20
  it("replaces #{a.inspect} with #{b.inspect}") { get(a).body.should == b }
21
21
  end
22
+
23
+ it 'should be able to deal with PATH_INFO = nil (fcgi?)' do
24
+ app = Rack::Protection::PathTraversal.new(proc { 42 })
25
+ app.call({}).should be == 42
26
+ end
27
+ end
28
+
29
+ if "".respond_to?(:encoding) # Ruby 1.9+ M17N
30
+ context "PATH_INFO's encoding" do
31
+ before do
32
+ @app = Rack::Protection::PathTraversal.new(proc { |e| [200, {'Content-Type' => 'text/plain'}, [e['PATH_INFO'].encoding.to_s]] })
33
+ end
34
+
35
+ it 'should remain unchanged as ASCII-8BIT' do
36
+ body = @app.call({ 'PATH_INFO' => '/'.encode('ASCII-8BIT') })[2][0]
37
+ body.should == 'ASCII-8BIT'
38
+ end
39
+ end
22
40
  end
23
41
  end
@@ -2,4 +2,104 @@ require File.expand_path('../spec_helper.rb', __FILE__)
2
2
 
3
3
  describe Rack::Protection do
4
4
  it_behaves_like "any rack application"
5
+
6
+ it 'passes on options' do
7
+ mock_app do
8
+ use Rack::Protection, :track => ['HTTP_FOO']
9
+ run proc { |e| [200, {'Content-Type' => 'text/plain'}, ['hi']] }
10
+ end
11
+
12
+ session = {:foo => :bar}
13
+ get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'a'
14
+ get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'b'
15
+ session[:foo].should be == :bar
16
+
17
+ get '/', {}, 'rack.session' => session, 'HTTP_FOO' => 'BAR'
18
+ session.should be_empty
19
+ end
20
+
21
+ it 'passes errors through if :reaction => :report is used' do
22
+ mock_app do
23
+ use Rack::Protection, :reaction => :report
24
+ run proc { |e| [200, {'Content-Type' => 'text/plain'}, [e["protection.failed"].to_s]] }
25
+ end
26
+
27
+ session = {:foo => :bar}
28
+ post('/', {}, 'rack.session' => session, 'HTTP_ORIGIN' => 'http://malicious.com')
29
+ last_response.should be_ok
30
+ body.should == "true"
31
+ end
32
+
33
+ describe "#react" do
34
+ it 'prevents attacks and warns about it' do
35
+ io = StringIO.new
36
+ mock_app do
37
+ use Rack::Protection, :logger => Logger.new(io)
38
+ run DummyApp
39
+ end
40
+ post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com')
41
+ io.string.should match /prevented.*Origin/
42
+ end
43
+
44
+ it 'reports attacks if reaction is to report' do
45
+ io = StringIO.new
46
+ mock_app do
47
+ use Rack::Protection, :reaction => :report, :logger => Logger.new(io)
48
+ run DummyApp
49
+ end
50
+ post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com')
51
+ io.string.should match /reported.*Origin/
52
+ io.string.should_not match /prevented.*Origin/
53
+ end
54
+
55
+ it 'passes errors to reaction method if specified' do
56
+ io = StringIO.new
57
+ Rack::Protection::Base.send(:define_method, :special) { |*args| io << args.inspect }
58
+ mock_app do
59
+ use Rack::Protection, :reaction => :special, :logger => Logger.new(io)
60
+ run DummyApp
61
+ end
62
+ post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com')
63
+ io.string.should match /HTTP_ORIGIN.*malicious.com/
64
+ io.string.should_not match /reported|prevented/
65
+ end
66
+ end
67
+
68
+ describe "#html?" do
69
+ context "given an appropriate content-type header" do
70
+ subject { Rack::Protection::Base.new(nil).html? 'content-type' => "text/html" }
71
+ it { should be_true }
72
+ end
73
+
74
+ context "given an inappropriate content-type header" do
75
+ subject { Rack::Protection::Base.new(nil).html? 'content-type' => "image/gif" }
76
+ it { should be_false }
77
+ end
78
+
79
+ context "given no content-type header" do
80
+ subject { Rack::Protection::Base.new(nil).html?({}) }
81
+ it { should be_false }
82
+ end
83
+ end
84
+
85
+ describe "#instrument" do
86
+ let(:env) { { 'rack.protection.attack' => 'base' } }
87
+ let(:instrumenter) { double('Instrumenter') }
88
+
89
+ after do
90
+ app.instrument(env)
91
+ end
92
+
93
+ context 'with an instrumenter specified' do
94
+ let(:app) { Rack::Protection::Base.new(nil, :instrumenter => instrumenter) }
95
+
96
+ it { instrumenter.should_receive(:instrument).with('rack.protection', env) }
97
+ end
98
+
99
+ context 'with no instrumenter specified' do
100
+ let(:app) { Rack::Protection::Base.new(nil) }
101
+
102
+ it { instrumenter.should_not_receive(:instrument) }
103
+ end
104
+ end
5
105
  end
@@ -17,11 +17,12 @@ describe Rack::Protection::SessionHijacking do
17
17
  session.should be_empty
18
18
  end
19
19
 
20
- it "denies requests with a changing Accept-Encoding header" do
20
+ it "accepts requests with a changing Accept-Encoding header" do
21
+ # this is tested because previously it led to clearing the session
21
22
  session = {:foo => :bar}
22
23
  get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'a'
23
24
  get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'b'
24
- session.should be_empty
25
+ session.should_not be_empty
25
26
  end
26
27
 
27
28
  it "denies requests with a changing Accept-Language header" do
@@ -31,10 +32,24 @@ describe Rack::Protection::SessionHijacking do
31
32
  session.should be_empty
32
33
  end
33
34
 
34
- it "denies requests with a changing Version header"do
35
+ it "accepts requests with the same Accept-Language header" do
36
+ session = {:foo => :bar}
37
+ get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'a'
38
+ get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'a'
39
+ session.should_not be_empty
40
+ end
41
+
42
+ it "comparison of Accept-Language header is not case sensitive" do
43
+ session = {:foo => :bar}
44
+ get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'a'
45
+ get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'A'
46
+ session.should_not be_empty
47
+ end
48
+
49
+ it "accepts requests with a changing Version header"do
35
50
  session = {:foo => :bar}
36
51
  get '/', {}, 'rack.session' => session, 'HTTP_VERSION' => '1.0'
37
52
  get '/', {}, 'rack.session' => session, 'HTTP_VERSION' => '1.1'
38
- session.should be_empty
53
+ session[:foo].should == :bar
39
54
  end
40
55
  end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'rack/protection'
2
2
  require 'rack/test'
3
+ require 'rack'
3
4
  require 'forwardable'
4
5
  require 'stringio'
5
6
 
@@ -21,10 +22,15 @@ if version == "1.3"
21
22
  end
22
23
  end
23
24
 
25
+ unless Rack::MockResponse.method_defined? :header
26
+ Rack::MockResponse.send(:alias_method, :header, :headers)
27
+ end
28
+
24
29
  module DummyApp
25
30
  def self.call(env)
26
31
  Thread.current[:last_env] = env
27
- [200, {'Content-Type' => 'text/plain'}, ['ok']]
32
+ body = (env['REQUEST_METHOD'] == 'HEAD' ? '' : 'ok')
33
+ [200, {'Content-Type' => env['wants'] || 'text/plain'}, [body]]
28
34
  end
29
35
  end
30
36
 
@@ -4,7 +4,15 @@ describe Rack::Protection::XSSHeader do
4
4
  it_behaves_like "any rack application"
5
5
 
6
6
  it 'should set the X-XSS-Protection' do
7
- get('/').headers["X-XSS-Protection"].should == "1; mode=block"
7
+ get('/', {}, 'wants' => 'text/html;charset=utf-8').headers["X-XSS-Protection"].should == "1; mode=block"
8
+ end
9
+
10
+ it 'should set the X-XSS-Protection for XHTML' do
11
+ get('/', {}, 'wants' => 'application/xhtml+xml').headers["X-XSS-Protection"].should == "1; mode=block"
12
+ end
13
+
14
+ it 'should not set the X-XSS-Protection for other content types' do
15
+ get('/', {}, 'wants' => 'application/foo').headers["X-XSS-Protection"].should be_nil
8
16
  end
9
17
 
10
18
  it 'should allow changing the protection mode' do
@@ -14,11 +22,35 @@ describe Rack::Protection::XSSHeader do
14
22
  run DummyApp
15
23
  end
16
24
 
17
- get('/').headers["X-XSS-Protection"].should == "1; mode=foo"
25
+ get('/', {}, 'wants' => 'application/xhtml').headers["X-XSS-Protection"].should == "1; mode=foo"
18
26
  end
19
27
 
20
28
  it 'should not override the header if already set' do
21
29
  mock_app with_headers("X-XSS-Protection" => "0")
22
- get('/').headers["X-XSS-Protection"].should == "0"
30
+ get('/', {}, 'wants' => 'text/html').headers["X-XSS-Protection"].should == "0"
31
+ end
32
+
33
+ it 'should set the X-Content-Type-Options' do
34
+ get('/', {}, 'wants' => 'text/html').header["X-Content-Type-Options"].should == "nosniff"
35
+ end
36
+
37
+
38
+ it 'should set the X-Content-Type-Options for other content types' do
39
+ get('/', {}, 'wants' => 'application/foo').header["X-Content-Type-Options"].should == "nosniff"
40
+ end
41
+
42
+
43
+ it 'should allow changing the nosniff-mode off' do
44
+ mock_app do
45
+ use Rack::Protection::XSSHeader, :nosniff => false
46
+ run DummyApp
47
+ end
48
+
49
+ get('/').headers["X-Content-Type-Options"].should be_nil
50
+ end
51
+
52
+ it 'should not override the header if already set X-Content-Type-Options' do
53
+ mock_app with_headers("X-Content-Type-Options" => "sniff")
54
+ get('/', {}, 'wants' => 'text/html').headers["X-Content-Type-Options"].should == "sniff"
23
55
  end
24
56
  end