secure_headers 0.1.1 → 0.2.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.
Files changed (104) hide show
  1. data/.gitignore +9 -0
  2. data/.travis.yml +3 -2
  3. data/Gemfile +4 -0
  4. data/Guardfile +1 -1
  5. data/HISTORY.md +14 -0
  6. data/README.md +2 -2
  7. data/Rakefile +41 -1
  8. data/app/controllers/content_security_policy_controller.rb +19 -10
  9. data/fixtures/rails_3_2_12/.rspec +1 -0
  10. data/fixtures/rails_3_2_12/Gemfile +14 -0
  11. data/fixtures/rails_3_2_12/Guardfile +14 -0
  12. data/fixtures/rails_3_2_12/README.rdoc +261 -0
  13. data/fixtures/rails_3_2_12/Rakefile +7 -0
  14. data/fixtures/rails_3_2_12/app/controllers/application_controller.rb +4 -0
  15. data/fixtures/rails_3_2_12/app/controllers/other_things_controller.rb +5 -0
  16. data/fixtures/rails_3_2_12/app/controllers/things_controller.rb +6 -0
  17. data/fixtures/rails_3_2_12/app/models/.gitkeep +0 -0
  18. data/fixtures/rails_3_2_12/app/models/thing.rb +3 -0
  19. data/fixtures/rails_3_2_12/app/views/layouts/application.html.erb +14 -0
  20. data/fixtures/rails_3_2_12/app/views/other_things/index.html.erb +1 -0
  21. data/fixtures/rails_3_2_12/app/views/things/index.html.erb +21 -0
  22. data/fixtures/rails_3_2_12/config.ru +4 -0
  23. data/fixtures/rails_3_2_12/config/application.rb +68 -0
  24. data/fixtures/rails_3_2_12/config/boot.rb +6 -0
  25. data/fixtures/rails_3_2_12/config/database.yml +25 -0
  26. data/fixtures/rails_3_2_12/config/environment.rb +5 -0
  27. data/fixtures/rails_3_2_12/config/environments/development.rb +37 -0
  28. data/fixtures/rails_3_2_12/config/environments/production.rb +67 -0
  29. data/fixtures/rails_3_2_12/config/environments/test.rb +37 -0
  30. data/fixtures/rails_3_2_12/config/initializers/backtrace_silencers.rb +7 -0
  31. data/fixtures/rails_3_2_12/config/initializers/inflections.rb +15 -0
  32. data/fixtures/rails_3_2_12/config/initializers/mime_types.rb +5 -0
  33. data/fixtures/rails_3_2_12/config/initializers/secret_token.rb +7 -0
  34. data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +15 -0
  35. data/fixtures/rails_3_2_12/config/initializers/session_store.rb +8 -0
  36. data/fixtures/rails_3_2_12/config/initializers/wrap_parameters.rb +14 -0
  37. data/fixtures/rails_3_2_12/config/locales/en.yml +5 -0
  38. data/fixtures/rails_3_2_12/config/routes.rb +61 -0
  39. data/fixtures/rails_3_2_12/db/schema.rb +16 -0
  40. data/fixtures/rails_3_2_12/db/seeds.rb +7 -0
  41. data/fixtures/rails_3_2_12/lib/assets/.gitkeep +0 -0
  42. data/fixtures/rails_3_2_12/lib/tasks/.gitkeep +0 -0
  43. data/fixtures/rails_3_2_12/log/.gitkeep +0 -0
  44. data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +40 -0
  45. data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +47 -0
  46. data/fixtures/rails_3_2_12/spec/spec_helper.rb +19 -0
  47. data/fixtures/rails_3_2_12/vendor/assets/javascripts/.gitkeep +0 -0
  48. data/fixtures/rails_3_2_12/vendor/assets/stylesheets/.gitkeep +0 -0
  49. data/fixtures/rails_3_2_12/vendor/plugins/.gitkeep +0 -0
  50. data/fixtures/rails_3_2_12_no_init/.rspec +1 -0
  51. data/fixtures/rails_3_2_12_no_init/Gemfile +14 -0
  52. data/fixtures/rails_3_2_12_no_init/Guardfile +14 -0
  53. data/fixtures/rails_3_2_12_no_init/README.rdoc +261 -0
  54. data/fixtures/rails_3_2_12_no_init/Rakefile +7 -0
  55. data/fixtures/rails_3_2_12_no_init/app/controllers/application_controller.rb +4 -0
  56. data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +7 -0
  57. data/fixtures/rails_3_2_12_no_init/app/controllers/things_controller.rb +5 -0
  58. data/fixtures/rails_3_2_12_no_init/app/models/.gitkeep +0 -0
  59. data/fixtures/rails_3_2_12_no_init/app/models/thing.rb +3 -0
  60. data/fixtures/rails_3_2_12_no_init/app/views/layouts/application.html.erb +14 -0
  61. data/fixtures/rails_3_2_12_no_init/app/views/other_things/index.html.erb +1 -0
  62. data/fixtures/rails_3_2_12_no_init/app/views/things/_form.html.erb +17 -0
  63. data/fixtures/rails_3_2_12_no_init/app/views/things/edit.html.erb +6 -0
  64. data/fixtures/rails_3_2_12_no_init/app/views/things/index.html.erb +21 -0
  65. data/fixtures/rails_3_2_12_no_init/app/views/things/new.html.erb +5 -0
  66. data/fixtures/rails_3_2_12_no_init/app/views/things/show.html.erb +5 -0
  67. data/fixtures/rails_3_2_12_no_init/config.ru +4 -0
  68. data/fixtures/rails_3_2_12_no_init/config/application.rb +68 -0
  69. data/fixtures/rails_3_2_12_no_init/config/boot.rb +6 -0
  70. data/fixtures/rails_3_2_12_no_init/config/database.yml +25 -0
  71. data/fixtures/rails_3_2_12_no_init/config/environment.rb +5 -0
  72. data/fixtures/rails_3_2_12_no_init/config/environments/development.rb +37 -0
  73. data/fixtures/rails_3_2_12_no_init/config/environments/production.rb +67 -0
  74. data/fixtures/rails_3_2_12_no_init/config/environments/test.rb +37 -0
  75. data/fixtures/rails_3_2_12_no_init/config/initializers/backtrace_silencers.rb +7 -0
  76. data/fixtures/rails_3_2_12_no_init/config/initializers/inflections.rb +15 -0
  77. data/fixtures/rails_3_2_12_no_init/config/initializers/mime_types.rb +5 -0
  78. data/fixtures/rails_3_2_12_no_init/config/initializers/secret_token.rb +7 -0
  79. data/fixtures/rails_3_2_12_no_init/config/initializers/session_store.rb +8 -0
  80. data/fixtures/rails_3_2_12_no_init/config/initializers/wrap_parameters.rb +14 -0
  81. data/fixtures/rails_3_2_12_no_init/config/locales/en.yml +5 -0
  82. data/fixtures/rails_3_2_12_no_init/config/routes.rb +61 -0
  83. data/fixtures/rails_3_2_12_no_init/db/schema.rb +16 -0
  84. data/fixtures/rails_3_2_12_no_init/db/seeds.rb +7 -0
  85. data/fixtures/rails_3_2_12_no_init/lib/assets/.gitkeep +0 -0
  86. data/fixtures/rails_3_2_12_no_init/lib/tasks/.gitkeep +0 -0
  87. data/fixtures/rails_3_2_12_no_init/log/.gitkeep +0 -0
  88. data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +40 -0
  89. data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +44 -0
  90. data/fixtures/rails_3_2_12_no_init/spec/spec_helper.rb +20 -0
  91. data/fixtures/rails_3_2_12_no_init/vendor/assets/javascripts/.gitkeep +0 -0
  92. data/fixtures/rails_3_2_12_no_init/vendor/assets/stylesheets/.gitkeep +0 -0
  93. data/fixtures/rails_3_2_12_no_init/vendor/plugins/.gitkeep +0 -0
  94. data/lib/secure_headers.rb +19 -15
  95. data/lib/secure_headers/headers/content_security_policy.rb +54 -113
  96. data/lib/secure_headers/headers/content_security_policy/browser_strategy.rb +70 -0
  97. data/lib/secure_headers/headers/content_security_policy/firefox_browser_strategy.rb +72 -0
  98. data/lib/secure_headers/headers/content_security_policy/ie_browser_strategy.rb +6 -0
  99. data/lib/secure_headers/headers/content_security_policy/webkit_browser_strategy.rb +9 -0
  100. data/lib/secure_headers/version.rb +1 -1
  101. data/{secure-headers.gemspec → secure_headers.gemspec} +0 -0
  102. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +72 -84
  103. data/travis.sh +10 -0
  104. metadata +93 -3
@@ -0,0 +1,5 @@
1
+ # Sample localization file for English. Add more files in this directory for other locales.
2
+ # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3
+
4
+ en:
5
+ hello: "Hello world"
@@ -0,0 +1,61 @@
1
+ Rails3212::Application.routes.draw do
2
+ resources :things
3
+
4
+
5
+ # The priority is based upon order of creation:
6
+ # first created -> highest priority.
7
+
8
+ # Sample of regular route:
9
+ # match 'products/:id' => 'catalog#view'
10
+ # Keep in mind you can assign values other than :controller and :action
11
+
12
+ # Sample of named route:
13
+ # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
14
+ # This route can be invoked with purchase_url(:id => product.id)
15
+
16
+ # Sample resource route (maps HTTP verbs to controller actions automatically):
17
+ # resources :products
18
+
19
+ # Sample resource route with options:
20
+ # resources :products do
21
+ # member do
22
+ # get 'short'
23
+ # post 'toggle'
24
+ # end
25
+ #
26
+ # collection do
27
+ # get 'sold'
28
+ # end
29
+ # end
30
+
31
+ # Sample resource route with sub-resources:
32
+ # resources :products do
33
+ # resources :comments, :sales
34
+ # resource :seller
35
+ # end
36
+
37
+ # Sample resource route with more complex sub-resources
38
+ # resources :products do
39
+ # resources :comments
40
+ # resources :sales do
41
+ # get 'recent', :on => :collection
42
+ # end
43
+ # end
44
+
45
+ # Sample resource route within a namespace:
46
+ # namespace :admin do
47
+ # # Directs /admin/products/* to Admin::ProductsController
48
+ # # (app/controllers/admin/products_controller.rb)
49
+ # resources :products
50
+ # end
51
+
52
+ # You can have the root of your site routed with "root"
53
+ # just remember to delete public/index.html.
54
+ # root :to => 'welcome#index'
55
+
56
+ # See how all your routes lay out with "rake routes"
57
+
58
+ # This is a legacy wild controller route that's not recommended for RESTful applications.
59
+ # Note: This route will make all actions in every controller accessible via GET requests.
60
+ match ':controller(/:action(/:id))(.:format)'
61
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: UTF-8
2
+ # This file is auto-generated from the current state of the database. Instead
3
+ # of editing this file, please use the migrations feature of Active Record to
4
+ # incrementally modify your database, and then regenerate this schema definition.
5
+ #
6
+ # Note that this schema.rb definition is the authoritative source for your
7
+ # database schema. If you need to create the application database on another
8
+ # system, you should be using db:schema:load, not running all the migrations
9
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
11
+ #
12
+ # It's strongly recommended to check this file into your version control system.
13
+
14
+ ActiveRecord::Schema.define(:version => 0) do
15
+
16
+ end
@@ -0,0 +1,7 @@
1
+ # This file should contain all the record creation needed to seed the database with its default values.
2
+ # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3
+ #
4
+ # Examples:
5
+ #
6
+ # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
7
+ # Mayor.create(name: 'Emanuel', city: cities.first)
File without changes
File without changes
File without changes
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe OtherThingsController do
4
+ describe "headers" do
5
+ before(:each) do
6
+ # Chrome
7
+ request.env['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5'
8
+ end
9
+
10
+ it "sets the X-XSS-PROTECTION header" do
11
+ get :index
12
+ response.headers['X-XSS-Protection'].should == SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE
13
+ end
14
+
15
+ it "sets the X-FRAME-OPTIONS header" do
16
+ get :index
17
+ response.headers['X-FRAME-OPTIONS'].should == SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE
18
+ end
19
+
20
+ it "sets the X-WebKit-CSP header" do
21
+ get :index
22
+ response.headers['X-WebKit-CSP-Report-Only'].should == "default-src 'self'; img-src data:;"
23
+ end
24
+
25
+ #mock ssl
26
+ it "sets the STRICT-TRANSPORT-SECURITY header" do
27
+ request.env['HTTPS'] = 'on'
28
+ get :index
29
+ response.headers['Strict-Transport-Security'].should == SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE
30
+ end
31
+
32
+ context "using IE" do
33
+ it "sets the X-CONTENT-TYPE-OPTIONS header" do
34
+ request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
35
+ get :index
36
+ response.headers['X-Content-Type-Options'].should == "nosniff"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ # This controller is meant to be something that inherits config from application controller
4
+ # all values are defaulted because no initializer is configured, and the values in app controller
5
+ # only provide csp => false
6
+
7
+ describe ThingsController do
8
+ describe "headers" do
9
+ before(:each) do
10
+ # Chrome
11
+ request.env['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5'
12
+ end
13
+
14
+ it "sets the X-XSS-PROTECTION header" do
15
+ get :index
16
+ response.headers['X-XSS-Protection'].should == SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE
17
+ end
18
+
19
+ it "sets the X-FRAME-OPTIONS header" do
20
+ get :index
21
+ response.headers['X-FRAME-OPTIONS'].should == SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE
22
+ end
23
+
24
+ it "sets the X-WebKit-CSP header" do
25
+ get :index
26
+ response.headers['X-WebKit-CSP-Report-Only'].should == nil
27
+ end
28
+
29
+ #mock ssl
30
+ it "sets the STRICT-TRANSPORT-SECURITY header" do
31
+ request.env['HTTPS'] = 'on'
32
+ get :index
33
+ response.headers['Strict-Transport-Security'].should == SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE
34
+ end
35
+
36
+ context "using IE" do
37
+ it "sets the X-CONTENT-TYPE-OPTIONS header" do
38
+ request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
39
+ get :index
40
+ response.headers['X-Content-Type-Options'].should == "nosniff"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,20 @@
1
+ require 'rubygems'
2
+ require 'spork'
3
+ #uncomment the following line to use spork with the debugger
4
+ #require 'spork/ext/ruby-debug'
5
+
6
+ Spork.prefork do
7
+ # Loading more in this block will cause your tests to run faster. However,
8
+ # if you change any configuration or code from libraries loaded here, you'll
9
+ # need to restart spork for it take effect.
10
+ # This file is copied to spec/ when you run 'rails generate rspec:install'
11
+ ENV["RAILS_ENV"] ||= 'test'
12
+ require File.expand_path("../../config/environment", __FILE__)
13
+ require 'rspec/rails'
14
+ require 'rspec/autorun'
15
+ # require 'ruby-debug'
16
+ end
17
+
18
+ Spork.each_run do
19
+
20
+ end
@@ -13,25 +13,25 @@ module SecureHeaders
13
13
  class << self
14
14
  def append_features(base)
15
15
  base.module_eval do
16
- @@secure_headers_options = nil
17
-
18
16
  extend ClassMethods
19
17
  include InstanceMethods
20
-
21
- # jank?
22
- def self.secure_headers_options=(opts)
23
- @@secure_headers_options = opts
24
- end
25
-
26
- def self.secure_headers_options
27
- @@secure_headers_options
28
- end
29
18
  end
30
19
  end
31
20
  end
32
21
 
33
22
  module ClassMethods
34
- def ensure_security_headers options = {}, *args
23
+ attr_writer :secure_headers_options
24
+ def secure_headers_options
25
+ if @secure_headers_options
26
+ @secure_headers_options
27
+ elsif superclass.respond_to?(:secure_headers_options) # stop at application_controller
28
+ superclass.secure_headers_options
29
+ else
30
+ {}
31
+ end
32
+ end
33
+
34
+ def ensure_security_headers options = {}
35
35
  self.secure_headers_options = options
36
36
  before_filter :set_security_headers
37
37
  end
@@ -44,7 +44,7 @@ module SecureHeaders
44
44
  end
45
45
 
46
46
  module InstanceMethods
47
- def set_security_headers(options = self.class.secure_headers_options || {})
47
+ def set_security_headers(options = self.class.secure_headers_options)
48
48
  brwsr = Brwsr::Browser.new(:ua => request.env['HTTP_USER_AGENT'])
49
49
  set_hsts_header(options[:hsts]) if request.ssl?
50
50
  set_x_frame_options_header(options[:x_frame_options])
@@ -59,10 +59,10 @@ module SecureHeaders
59
59
  options = self.class.options_for :csp, options
60
60
  return if options == false
61
61
 
62
- header = ContentSecurityPolicy.new(request, options)
62
+ header = ContentSecurityPolicy.new(options, :request => request)
63
63
  set_header(header.name, header.value)
64
64
  if options && options[:experimental] && options[:enforce]
65
- header = ContentSecurityPolicy.new(request, options, :experimental => true)
65
+ header = ContentSecurityPolicy.new(options, :experimental => true, :request => request)
66
66
  set_header(header.name, header.value)
67
67
  end
68
68
  end
@@ -107,6 +107,10 @@ end
107
107
 
108
108
  require "secure_headers/version"
109
109
  require "secure_headers/headers/content_security_policy"
110
+ require "secure_headers/headers/content_security_policy/browser_strategy"
111
+ require "secure_headers/headers/content_security_policy/firefox_browser_strategy"
112
+ require "secure_headers/headers/content_security_policy/ie_browser_strategy"
113
+ require "secure_headers/headers/content_security_policy/webkit_browser_strategy"
110
114
  require "secure_headers/headers/x_frame_options"
111
115
  require "secure_headers/headers/strict_transport_security"
112
116
  require "secure_headers/headers/x_xss_protection"
@@ -19,27 +19,39 @@ module SecureHeaders
19
19
  end
20
20
  include Constants
21
21
 
22
- META.each do |meta|
23
- attr_accessor meta
24
- end
25
- attr_reader :browser, :ssl_request, :report_uri, :request_uri
22
+ attr_accessor *META
23
+ attr_reader :browser, :ssl_request, :report_uri, :request_uri, :experimental, :config
26
24
 
27
- alias :enforce? :enforce
28
25
  alias :disable_chrome_extension? :disable_chrome_extension
29
26
  alias :disable_fill_missing? :disable_fill_missing
30
27
  alias :ssl_request? :ssl_request
31
28
 
32
-
33
- def initialize(request=nil, config=nil, options={})
29
+ # +options+ param contains
30
+ # :experimental use experimental block for config
31
+ # :ssl_request used to determine if http_additions should be used
32
+ # :request_uri used to determine if firefox should send the report directly
33
+ # or use the forwarding endpoint
34
+ # :ua the user agent (or just use Firefox/Chrome/MSIE/etc)
35
+ #
36
+ # :report used to determine what :ssl_request, :ua, and :request_uri are set to
37
+ def initialize(config=nil, options={})
34
38
  @experimental = !!options.delete(:experimental)
35
- if config
36
- configure request, config
37
- elsif request
38
- parse_request request
39
+ if options[:request]
40
+ parse_request(options[:request])
41
+ else
42
+ @browser = Brwsr::Browser.new(:ua => options[:ua])
43
+ # fails open, assumes http. Bad idea? Will always include http additions.
44
+ # could also fail if not supplied.
45
+ @ssl_request = !!options.delete(:ssl)
46
+ # a nil value here means we always assume we are not on the same host,
47
+ # which causes all FF csp reports to go through the forwarder
48
+ @request_uri = options.delete(:request_uri)
39
49
  end
50
+
51
+ configure(config) if config
40
52
  end
41
53
 
42
- def configure request, opts
54
+ def configure opts
43
55
  @config = opts.dup
44
56
 
45
57
  experimental_config = @config.delete(:experimental)
@@ -48,9 +60,8 @@ module SecureHeaders
48
60
  @config.merge!(experimental_config)
49
61
  end
50
62
 
51
- parse_request request
52
63
  META.each do |meta|
53
- self.send(meta.to_s + "=", @config.delete(meta))
64
+ self.send("#{meta}=", @config.delete(meta))
54
65
  end
55
66
 
56
67
  @report_uri = @config.delete(:report_uri)
@@ -61,45 +72,39 @@ module SecureHeaders
61
72
  end
62
73
 
63
74
  def name
64
- base = if browser.ie?
65
- STANDARD_HEADER_NAME
66
- elsif browser.firefox?
67
- # can't use supports_standard because FF18 does not support this part of the standard.
68
- FIREFOX_CSP_HEADER_NAME
69
- else
70
- WEBKIT_CSP_HEADER_NAME
71
- end
72
-
73
- if !enforce || @experimental
74
- base += "-Report-Only"
75
- end
76
- base
75
+ browser_strategy.name
77
76
  end
78
77
 
79
78
  def value
80
79
  return @config if @config.is_a?(String)
81
- if @config.nil?
82
- return supports_standard? ? WEBKIT_CSP_HEADER : FIREFOX_CSP_HEADER
80
+
81
+ if @config
82
+ build_value
83
+ else
84
+ browser_strategy.csp_header
83
85
  end
86
+ end
87
+
88
+ private
84
89
 
85
- build_value
90
+ def browser_strategy
91
+ @browser_strategy ||= BrowserStrategy.build(self)
86
92
  end
87
93
 
88
94
  def directives
89
- # can't use supports_standard because FF18 does not support this part of the standard.
90
- browser.firefox? ? FIREFOX_DIRECTIVES : WEBKIT_DIRECTIVES
95
+ browser_strategy.directives
91
96
  end
92
97
 
93
- private
94
-
95
98
  def build_value
96
99
  fill_directives unless disable_fill_missing?
97
100
  add_missing_chrome_extension_values unless disable_chrome_extension?
98
101
  append_http_additions unless ssl_request?
99
102
 
100
- header_value = build_impl_specific_directives
101
- header_value += generic_directives(@config)
102
- header_value += report_uri_directive(@report_uri)
103
+ header_value = [
104
+ build_impl_specific_directives,
105
+ generic_directives(@config),
106
+ report_uri_directive(@report_uri)
107
+ ].join
103
108
 
104
109
  #store the value for next time
105
110
  @config = header_value
@@ -148,12 +153,7 @@ module SecureHeaders
148
153
  end
149
154
 
150
155
  def filter_unsupported_directives
151
- if browser.firefox?
152
- # can't use supports_standard because FF18 does not support this part of the standard.
153
- @config[:xhr_src] = @config.delete(:connect_src) if @config[:connect_src]
154
- else
155
- @config.delete(:frame_ancestors)
156
- end
156
+ @config = browser_strategy.filter_unsupported_directives(@config)
157
157
  end
158
158
 
159
159
  # translates 'inline','self', 'none' and 'eval' to their respective impl-specific values.
@@ -168,104 +168,45 @@ module SecureHeaders
168
168
  end
169
169
  end
170
170
 
171
- # inline/eval => impl-specific values
172
171
  def translate_inline_or_eval val
173
- # can't use supports_standard because FF18 does not support this part of the standard.
174
- if browser.firefox?
175
- val == 'inline' ? 'inline-script' : 'eval-script'
176
- else
177
- val == 'inline' ? "'unsafe-inline'" : "'unsafe-eval'"
178
- end
172
+ browser_strategy.translate_inline_or_eval(val)
179
173
  end
180
174
 
181
175
  # if we have a forwarding endpoint setup and we are not on the same origin as our report_uri
182
176
  # or only a path was supplied (in which case we assume cross-host)
183
177
  # we need to forward the request for Firefox.
184
178
  def normalize_reporting_endpoint
185
- if browser.firefox? && (!same_origin? || URI.parse(report_uri).host.nil?)
186
- if forward_endpoint
187
- @report_uri = FF_CSP_ENDPOINT
188
- else
189
- @report_uri = nil
190
- end
191
- end
192
- end
179
+ return unless browser_strategy.normalize_reporting_endpoint?
180
+ return unless !same_origin? || URI.parse(report_uri).host.nil?
193
181
 
194
- def supports_standard?
195
- !browser.firefox?
196
- end
197
-
198
- def build_impl_specific_directives
199
- header_value = ""
200
- default = expect_directive_value(:default_src)
201
- # firefox 18 still requires the use of the options value, but can substitute default-src for allow
202
- if browser.firefox?
203
- header_value += build_firefox_specific_preamble(default) || ''
182
+ if forward_endpoint
183
+ @report_uri = FF_CSP_ENDPOINT
204
184
  else
205
- header_value += "default-src #{default.join(" ")}; " if default.any?
185
+ @report_uri = nil
206
186
  end
207
-
208
- header_value
209
187
  end
210
188
 
211
- def build_firefox_specific_preamble(default_src_value)
212
- header_value = ''
213
- if supports_standard?
214
- header_value += "default-src #{default_src_value.join(" ")}; " if default_src_value.any?
215
- elsif default_src_value
216
- header_value += "allow #{default_src_value.join(" ")}; " if default_src_value.any?
217
- end
218
-
219
- options_directive = build_options_directive
220
- header_value += "options #{options_directive.join(" ")}; " if options_directive.any?
221
- header_value
189
+ def build_impl_specific_directives
190
+ default = expect_directive_value(:default_src)
191
+ browser_strategy.build_impl_specific_directives(default)
222
192
  end
223
193
 
224
194
  def expect_directive_value key
225
195
  @config.delete(key) {|k| raise ContentSecurityPolicyBuildError.new("Expected to find #{k} directive value")}
226
196
  end
227
197
 
228
- # moves inline/eval values from script-src to options
229
- # discards those values in the style-src directive
230
- def build_options_directive
231
- options_directive = []
232
- @config.each do |directive, val|
233
- next if val.is_a?(String)
234
- new_val = []
235
- val.each do |token|
236
- if ['inline-script', 'eval-script'].include?(token)
237
- # Firefox does not support blocking inline styles ATM
238
- # https://bugzilla.mozilla.org/show_bug.cgi?id=763879
239
- unless directive?(directive, "style_src") || options_directive.include?(token)
240
- options_directive << token
241
- end
242
- else
243
- new_val << token
244
- end
245
- end
246
- @config[directive] = new_val
247
- end
248
-
249
- options_directive
250
- end
251
-
252
198
  def same_origin?
253
- return if report_uri.nil?
199
+ return unless report_uri && request_uri
254
200
 
255
201
  origin = URI.parse(request_uri)
256
202
  uri = URI.parse(report_uri)
257
203
  uri.host == origin.host && origin.port == uri.port && origin.scheme == uri.scheme
258
204
  end
259
205
 
260
- def directive? val, name
261
- val.to_s.casecmp(name) == 0
262
- end
263
-
264
206
  def report_uri_directive(report_uri)
265
- report_uri.nil? ? '' : "report-uri #{report_uri};"
207
+ report_uri ? "report-uri #{report_uri};" : ''
266
208
  end
267
209
 
268
-
269
210
  def generic_directives(config)
270
211
  header_value = ''
271
212
  if config[:img_src]