http-security 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +1 -0
- data/.travis.yml +21 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +17 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +20 -0
- data/README.md +90 -0
- data/Rakefile +34 -0
- data/http-security.gemspec +23 -0
- data/lib/http/security.rb +2 -0
- data/lib/http/security/exceptions.rb +8 -0
- data/lib/http/security/headers.rb +12 -0
- data/lib/http/security/headers/cache_control.rb +36 -0
- data/lib/http/security/headers/content_security_policy.rb +71 -0
- data/lib/http/security/headers/content_security_policy_report_only.rb +10 -0
- data/lib/http/security/headers/pragma.rb +24 -0
- data/lib/http/security/headers/public_key_pins.rb +60 -0
- data/lib/http/security/headers/public_key_pins_report_only.rb +10 -0
- data/lib/http/security/headers/set_cookie.rb +75 -0
- data/lib/http/security/headers/strict_transport_security.rb +29 -0
- data/lib/http/security/headers/x_content_type_options.rb +24 -0
- data/lib/http/security/headers/x_frame_options.rb +39 -0
- data/lib/http/security/headers/x_permitted_cross_domain_policies.rb +47 -0
- data/lib/http/security/headers/x_xss_protection.rb +34 -0
- data/lib/http/security/http_date.rb +13 -0
- data/lib/http/security/malformed_header.rb +33 -0
- data/lib/http/security/parsers.rb +14 -0
- data/lib/http/security/parsers/cache_control.rb +62 -0
- data/lib/http/security/parsers/content_security_policy.rb +128 -0
- data/lib/http/security/parsers/content_security_policy_report_only.rb +10 -0
- data/lib/http/security/parsers/expires.rb +19 -0
- data/lib/http/security/parsers/parser.rb +408 -0
- data/lib/http/security/parsers/pragma.rb +25 -0
- data/lib/http/security/parsers/public_key_pins.rb +43 -0
- data/lib/http/security/parsers/public_key_pins_report_only.rb +10 -0
- data/lib/http/security/parsers/set_cookie.rb +62 -0
- data/lib/http/security/parsers/strict_transport_security.rb +42 -0
- data/lib/http/security/parsers/x_content_type_options.rb +19 -0
- data/lib/http/security/parsers/x_frame_options.rb +47 -0
- data/lib/http/security/parsers/x_permitted_cross_domain_policies.rb +33 -0
- data/lib/http/security/parsers/x_xss_protection.rb +27 -0
- data/lib/http/security/response.rb +323 -0
- data/lib/http/security/version.rb +5 -0
- data/spec/data/alexa.csv +100 -0
- data/spec/headers/cache_control_spec.rb +40 -0
- data/spec/headers/content_security_policy_spec.rb +46 -0
- data/spec/headers/pragma_spec.rb +26 -0
- data/spec/headers/public_key_pins_spec.rb +68 -0
- data/spec/headers/set_cookie_spec.rb +122 -0
- data/spec/headers/strict_transport_security_spec.rb +39 -0
- data/spec/headers/x_content_type_options_spec.rb +26 -0
- data/spec/headers/x_frame_options_spec.rb +86 -0
- data/spec/headers/x_permitted_cross_domain_policies_spec.rb +108 -0
- data/spec/headers/x_xss_protection_spec.rb +59 -0
- data/spec/parsers/cache_control_spec.rb +26 -0
- data/spec/parsers/content_security_policy_report_only_spec.rb +48 -0
- data/spec/parsers/content_security_policy_spec.rb +74 -0
- data/spec/parsers/expires_spec.rb +71 -0
- data/spec/parsers/parser_spec.rb +317 -0
- data/spec/parsers/pragma_spec.rb +10 -0
- data/spec/parsers/public_key_pins_spec.rb +81 -0
- data/spec/parsers/set_cookie_spec.rb +55 -0
- data/spec/parsers/strict_transport_security_spec.rb +62 -0
- data/spec/parsers/x_content_type_options_spec.rb +10 -0
- data/spec/parsers/x_frame_options_spec.rb +24 -0
- data/spec/parsers/x_permitted_cross_domain_policies_spec.rb +34 -0
- data/spec/parsers/x_xss_protection_spec.rb +39 -0
- data/spec/response_spec.rb +262 -0
- data/spec/spec_helper.rb +13 -0
- data/tasks/alexa.rb +40 -0
- metadata +171 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'http/security/parsers/parser'
|
2
|
+
|
3
|
+
module HTTP
|
4
|
+
module Security
|
5
|
+
module Parsers
|
6
|
+
class PublicKeyPins < Parser
|
7
|
+
rule :public_key_pins do
|
8
|
+
(
|
9
|
+
public_key_pin_directive >>
|
10
|
+
(semicolon >> public_key_pin_directive).repeat(0)
|
11
|
+
).as(:directives)
|
12
|
+
end
|
13
|
+
root :public_key_pins
|
14
|
+
|
15
|
+
rule(:public_key_pin_directive) do
|
16
|
+
pin |
|
17
|
+
max_age |
|
18
|
+
include_subdomains |
|
19
|
+
report_uri |
|
20
|
+
strict |
|
21
|
+
header_extension
|
22
|
+
end
|
23
|
+
|
24
|
+
rule(:pin) do
|
25
|
+
(
|
26
|
+
(stri('pin-') >> hash_algorithm).as(:key) |
|
27
|
+
(stri('pin-') >> unsupported_algorithm).as(:name)
|
28
|
+
) >> equals >> quoted_string.as(:value)
|
29
|
+
end
|
30
|
+
rule(:hash_algorithm) { stri('sha256') }
|
31
|
+
rule(:unsupported_algorithm) { token }
|
32
|
+
|
33
|
+
directive_rule :include_subdomains, 'includeSubDomains'
|
34
|
+
directive_rule :strict
|
35
|
+
|
36
|
+
rule(:report_uri) do
|
37
|
+
stri("report-uri").as(:key) >> equals >>
|
38
|
+
d_quote >> uri.as(:value) >> d_quote
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'http/security/parsers/parser'
|
2
|
+
|
3
|
+
module HTTP
|
4
|
+
module Security
|
5
|
+
module Parsers
|
6
|
+
class SetCookie < Parser
|
7
|
+
|
8
|
+
root :set_cookie
|
9
|
+
rule(:set_cookie) do
|
10
|
+
(cookie >> (str(', ') >> cookie).repeat(0)).as(:list)
|
11
|
+
end
|
12
|
+
|
13
|
+
rule(:cookie) do
|
14
|
+
(
|
15
|
+
cookie_pair.as(:cookie) >> (str('; ') >> cookie_av).repeat(0)
|
16
|
+
).as(:directives)
|
17
|
+
end
|
18
|
+
|
19
|
+
rule(:cookie_pair) do
|
20
|
+
cookie_name.as(:key) >> str('=') >> cookie_value.as(:value)
|
21
|
+
end
|
22
|
+
|
23
|
+
rule(:cookie_name) { token }
|
24
|
+
|
25
|
+
rule(:cookie_value) do
|
26
|
+
cookie_octet.repeat(0) |
|
27
|
+
str('"') >> cookie_octet.repeat(0) >> str('"')
|
28
|
+
end
|
29
|
+
|
30
|
+
# US-ASCII characters excluding CTLs,
|
31
|
+
# whitespace DQUOTE, comma, semicolon,
|
32
|
+
# and backslash
|
33
|
+
rule(:cookie_octet) do
|
34
|
+
match['\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e']
|
35
|
+
end
|
36
|
+
|
37
|
+
rule(:cookie_av) do
|
38
|
+
expires_av | max_age_av | domain_av | path_av | secure_av | httponly_av | extension_av
|
39
|
+
end
|
40
|
+
|
41
|
+
rule(:expires_av) { stri('Expires=') >> sane_cookie_date.as(:expires) }
|
42
|
+
rule(:sane_cookie_date) { http_date }
|
43
|
+
rule(:max_age_av) do
|
44
|
+
stri('Max-Age=') >> (non_zero_digit >> digit.repeat(0)).as(:max_age)
|
45
|
+
end
|
46
|
+
|
47
|
+
rule(:non_zero_digit) { match['\x31-\x39'] } # 1-9
|
48
|
+
rule(:domain_av) { stri('Domain=') >> domain_value.as(:domain) }
|
49
|
+
rule(:domain_value) { host_name }
|
50
|
+
|
51
|
+
rule(:path_av) { stri('Path=') >> path_value.as(:path) }
|
52
|
+
# <any CHAR except CTLs or ";">
|
53
|
+
rule(:path_value) { match['^\x00-\x1f\x7f;'].repeat(0) }
|
54
|
+
rule(:secure_av) { stri('Secure').as(:secure) }
|
55
|
+
rule(:httponly_av) { stri('HttpOnly').as(:http_only) }
|
56
|
+
# <any CHAR except CTLs or ";">
|
57
|
+
rule(:extension_av) { match['^\x00-\x1f\x7f;'].repeat(0) }
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'http/security/parsers/parser'
|
2
|
+
|
3
|
+
module HTTP
|
4
|
+
module Security
|
5
|
+
module Parsers
|
6
|
+
class StrictTransportSecurity < Parser
|
7
|
+
# Strict-Transport-Security
|
8
|
+
# Syntax:
|
9
|
+
# Strict-Transport-Security = "Strict-Transport-Security" ":"
|
10
|
+
# [ directive ] *( ";" [ directive ] )
|
11
|
+
#
|
12
|
+
# directive = directive-name [ "=" directive-value ]
|
13
|
+
# directive-name = token
|
14
|
+
# directive-value = token | quoted-string
|
15
|
+
#
|
16
|
+
# where:
|
17
|
+
#
|
18
|
+
# token = <token, defined in [RFC2616], Section 2.2>
|
19
|
+
# quoted-string = <quoted-string, defined in [RFC2616], Section 2.2>
|
20
|
+
#
|
21
|
+
# REQUIRED directives: max-age
|
22
|
+
# OPTIONAL directives: includeSubdomains
|
23
|
+
rule(:strict_transport_security) do
|
24
|
+
(
|
25
|
+
(max_age.absent? >> (stp_header_extension >> wsp? >> semicolon >> wsp?)).repeat(0) >>
|
26
|
+
max_age >> (semicolon >> stp_header_extension).repeat(0)
|
27
|
+
).as(:directives)
|
28
|
+
end
|
29
|
+
root :strict_transport_security
|
30
|
+
|
31
|
+
rule(:stp_header_extension) do
|
32
|
+
include_sub_domains | (
|
33
|
+
token.as(:name) >>
|
34
|
+
(equals >> (token | quoted_string).as(:value)).maybe
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
directive_rule :include_sub_domains, 'includeSubDomains'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'http/security/parsers/parser'
|
2
|
+
|
3
|
+
module HTTP
|
4
|
+
module Security
|
5
|
+
module Parsers
|
6
|
+
class XContentTypeOptions < Parser
|
7
|
+
# X-Content-Type-Options
|
8
|
+
# Syntax:
|
9
|
+
# X-Content-Type-Options: nosniff
|
10
|
+
rule(:x_content_type_options) do
|
11
|
+
no_sniff.as(:directives)
|
12
|
+
end
|
13
|
+
root :x_content_type_options
|
14
|
+
|
15
|
+
directive_rule :no_sniff, 'nosniff'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'http/security/parsers/parser'
|
2
|
+
|
3
|
+
module HTTP
|
4
|
+
module Security
|
5
|
+
module Parsers
|
6
|
+
class XFrameOptions < Parser
|
7
|
+
# X-Frame-Options
|
8
|
+
# Syntax:
|
9
|
+
# X-Frame-Options = "DENY"
|
10
|
+
# / "SAMEORIGIN"
|
11
|
+
# / ( "ALLOW-FROM" RWS SERIALIZED-ORIGIN )
|
12
|
+
#
|
13
|
+
# RWS = 1*( SP / HTAB )
|
14
|
+
# ; required whitespace
|
15
|
+
# Only one can be present
|
16
|
+
rule(:x_frame_options) do
|
17
|
+
(
|
18
|
+
deny |
|
19
|
+
same_origin |
|
20
|
+
allow_from |
|
21
|
+
allow_all
|
22
|
+
).as(:directives)
|
23
|
+
end
|
24
|
+
root :x_frame_options
|
25
|
+
|
26
|
+
directive_rule :deny, 'deny'
|
27
|
+
directive_rule :same_origin, 'sameorigin'
|
28
|
+
directive_rule :allow_all, 'allowall'
|
29
|
+
|
30
|
+
rule(:allow_from) do
|
31
|
+
stri("allow-from").as(:key) >> wsp.repeat(1) >>
|
32
|
+
serialized_origin.as(:value)
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# URI
|
37
|
+
#
|
38
|
+
rule(:serialized_origin) do
|
39
|
+
(
|
40
|
+
scheme >> str(":") >> str("//") >> host_name >>
|
41
|
+
(str(":") >> digits.as(:port)).maybe
|
42
|
+
).as(:uri)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'http/security/parsers/parser'
|
2
|
+
|
3
|
+
module HTTP
|
4
|
+
module Security
|
5
|
+
module Parsers
|
6
|
+
class XPermittedCrossDomainPolicies < Parser
|
7
|
+
# X-Permitted-Cross-Domain-Policies
|
8
|
+
# Syntax:
|
9
|
+
# X-Permitted-Cross-Domain-Policies = "none"
|
10
|
+
# | master-only
|
11
|
+
# | by-content-type
|
12
|
+
# | by-ftp-filename
|
13
|
+
# | all
|
14
|
+
rule(:x_permitted_cross_domain_policies) do
|
15
|
+
(
|
16
|
+
none |
|
17
|
+
master_only |
|
18
|
+
by_content_type |
|
19
|
+
by_ftp_filename |
|
20
|
+
all
|
21
|
+
).as(:directives)
|
22
|
+
end
|
23
|
+
root :x_permitted_cross_domain_policies
|
24
|
+
|
25
|
+
directive_rule :none
|
26
|
+
directive_rule :master_only, 'master-only'
|
27
|
+
directive_rule :by_content_type, 'by-content-type'
|
28
|
+
directive_rule :by_ftp_filename, 'by-ftp-filename'
|
29
|
+
directive_rule :all
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'http/security/parsers/parser'
|
2
|
+
|
3
|
+
module HTTP
|
4
|
+
module Security
|
5
|
+
module Parsers
|
6
|
+
class XXSSProtection < Parser
|
7
|
+
# X-XSS-Protection
|
8
|
+
# Syntax:
|
9
|
+
# X-Content-Type-Options: < 1 | 0 >
|
10
|
+
# /; mode=block
|
11
|
+
rule(:x_xss_protection) do
|
12
|
+
x_xss_flag >> (semicolon >> x_xss_mode).maybe >> (semicolon >> x_xss_report).maybe
|
13
|
+
end
|
14
|
+
root :x_xss_protection
|
15
|
+
|
16
|
+
rule(:x_xss_flag) { match['01'].as(:boolean).as(:enabled) }
|
17
|
+
rule(:x_xss_mode) do
|
18
|
+
stri("mode") >> equals >> stri("block").as(:mode)
|
19
|
+
end
|
20
|
+
|
21
|
+
rule(:x_xss_report) do
|
22
|
+
stri('report') >> equals >> any.repeat(1).as(:report)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,323 @@
|
|
1
|
+
require 'http/security/exceptions'
|
2
|
+
require 'http/security/parsers'
|
3
|
+
require 'http/security/headers'
|
4
|
+
require 'http/security/malformed_header'
|
5
|
+
|
6
|
+
module HTTP
|
7
|
+
module Security
|
8
|
+
class Response
|
9
|
+
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
# The parsed `Cache-Control` header.
|
13
|
+
#
|
14
|
+
# @return [Headers::CacheControl]
|
15
|
+
attr_reader :cache_control
|
16
|
+
|
17
|
+
# The parsed `Content-Security-Policy` header.
|
18
|
+
#
|
19
|
+
# @return [Headers::ContentSecurityPolicy]
|
20
|
+
attr_reader :content_security_policy
|
21
|
+
|
22
|
+
# The parsed `Content-Security-Policy-Report-Only` header.
|
23
|
+
#
|
24
|
+
# @return [Headers::ContentSecurityPolicyReportOnly]
|
25
|
+
attr_reader :content_security_policy_report_only
|
26
|
+
|
27
|
+
# The parsed `Expires` header.
|
28
|
+
#
|
29
|
+
# @return [HTTPDate]
|
30
|
+
attr_reader :expires
|
31
|
+
|
32
|
+
# The parsed `Pragma` header.
|
33
|
+
#
|
34
|
+
# @return [Headers::Pagram]
|
35
|
+
attr_reader :pragma
|
36
|
+
|
37
|
+
# The parsed `Set-Cookie` header.
|
38
|
+
#
|
39
|
+
# @return [Headers::SetCookie]
|
40
|
+
attr_reader :set_cookie
|
41
|
+
|
42
|
+
# The parsed `Strict-Transport-Security` header.
|
43
|
+
#
|
44
|
+
# @return [Headers::StrictTransportSecurity]
|
45
|
+
attr_reader :strict_transport_security
|
46
|
+
|
47
|
+
# The parsed `Public-Key-Pins` header.
|
48
|
+
#
|
49
|
+
# @return [Headers::PublicKeyPin]
|
50
|
+
attr_reader :public_key_pins
|
51
|
+
|
52
|
+
# The parsed `Public-Key-Pins-Report-Only` header.
|
53
|
+
#
|
54
|
+
# @return [Headers::PublicKeyPinsReportOnly]
|
55
|
+
attr_reader :public_key_pins_report_only
|
56
|
+
|
57
|
+
# The parsed `X-Content-Type-Options` header.
|
58
|
+
#
|
59
|
+
# @return [Headers::XContentTypeOptions]
|
60
|
+
attr_reader :x_content_type_options
|
61
|
+
alias content_type_options x_content_type_options
|
62
|
+
|
63
|
+
# The parsed `X-Frame-Options` header.
|
64
|
+
#
|
65
|
+
# @return [Headers::XFrameOptions]
|
66
|
+
attr_reader :x_frame_options
|
67
|
+
alias frame_options x_frame_options
|
68
|
+
|
69
|
+
# The parsed `X-Permitted-Cross-Domain-Policies` header.
|
70
|
+
#
|
71
|
+
# @return [Headers::XPermittedCrossDomainPolicies]
|
72
|
+
attr_reader :x_permitted_cross_domain_policies
|
73
|
+
alias permitted_cross_domain_policies x_permitted_cross_domain_policies
|
74
|
+
|
75
|
+
# The parsed `X-XSS-Protection` header.
|
76
|
+
#
|
77
|
+
# @return [Headers::XXssProtection]
|
78
|
+
attr_reader :x_xss_protection
|
79
|
+
alias xss_protection x_xss_protection
|
80
|
+
|
81
|
+
#
|
82
|
+
# Initializes the response.
|
83
|
+
#
|
84
|
+
# @param [Hash{Symbol => Object}] headers
|
85
|
+
# The parsed headers.
|
86
|
+
#
|
87
|
+
# @option options [Hash] :cache_control
|
88
|
+
# The parsed `Cache-Control` header.
|
89
|
+
#
|
90
|
+
# @option options [Hash] :content_security_policy
|
91
|
+
# The parsed `Content-Security-Policy` header.
|
92
|
+
#
|
93
|
+
# @option options [Hash] :content_security_policy_report_only
|
94
|
+
# The parsed `Content-Security-Policy-Report-Only` header.
|
95
|
+
#
|
96
|
+
# @option options [Hash] :expires
|
97
|
+
# The parsed `Expires` header.
|
98
|
+
#
|
99
|
+
# @option options [Hash] :pragma
|
100
|
+
# The parsed `Pragma` header.
|
101
|
+
#
|
102
|
+
# @option options [Hash] :strict_transport_security
|
103
|
+
# The parsed `Strict-Transport-Security` header.
|
104
|
+
#
|
105
|
+
# @option options [Array<Hash>] :set_cookie
|
106
|
+
# The parsed `Set-Cookie` header.
|
107
|
+
#
|
108
|
+
# @option options [Hash] :public_key_pins
|
109
|
+
# The parsed `Public-Key-Pins` header.
|
110
|
+
#
|
111
|
+
# @option options [Hash] :public_key_pins_report_only
|
112
|
+
# The parsed `Public-Key-Pins-Report-Only` header.
|
113
|
+
#
|
114
|
+
# @option options [Hash] :x_content_type_options
|
115
|
+
# The parsed `X-Content-Type-Options` header.
|
116
|
+
#
|
117
|
+
# @option options [Hash] :x_frame_options
|
118
|
+
# The parsed `X-Frame-Options` header.
|
119
|
+
#
|
120
|
+
# @option options [Hash] :x_permitted_cross_domain_policies
|
121
|
+
# The parsed `X-Permitted-Cross-Domain-Policies` header.
|
122
|
+
#
|
123
|
+
# @option options [Hash] :x_xss_protection
|
124
|
+
# The parsed `X-XSS-Protection` header.
|
125
|
+
#
|
126
|
+
# @api semipublic
|
127
|
+
#
|
128
|
+
def initialize(headers={})
|
129
|
+
@cache_control = headers[:cache_control]
|
130
|
+
@content_security_policy = headers[:content_security_policy]
|
131
|
+
@content_security_policy_report_only = headers[:content_security_policy_report_only]
|
132
|
+
@expires = headers[:expires]
|
133
|
+
@pragma = headers[:pragma]
|
134
|
+
@public_key_pins = headers[:public_key_pins]
|
135
|
+
@public_key_pins_report_only = headers[:public_key_pins_report_only]
|
136
|
+
@strict_transport_security = headers[:strict_transport_security]
|
137
|
+
@set_cookie = headers[:set_cookie]
|
138
|
+
@x_content_type_options = headers[:x_content_type_options]
|
139
|
+
@x_frame_options = headers[:x_frame_options]
|
140
|
+
@x_permitted_cross_domain_policies = headers[:x_permitted_cross_domain_policies]
|
141
|
+
@x_xss_protection = headers[:x_xss_protection]
|
142
|
+
end
|
143
|
+
|
144
|
+
# Header names and their corresponding parsers.
|
145
|
+
PARSERS = {
|
146
|
+
'Cache-Control' => Parsers::CacheControl,
|
147
|
+
'Content-Security-Policy' => Parsers::ContentSecurityPolicy,
|
148
|
+
'Content-Security-Policy-Report-Only' => Parsers::ContentSecurityPolicyReportOnly,
|
149
|
+
'Expires' => Parsers::Expires,
|
150
|
+
'Pragma' => Parsers::Pragma,
|
151
|
+
'Public-Key-Pins' => Parsers::PublicKeyPins,
|
152
|
+
'Public-Key-Pins-Report-Only' => Parsers::PublicKeyPinsReportOnly,
|
153
|
+
'Strict-Transport-Security' => Parsers::StrictTransportSecurity,
|
154
|
+
'Set-Cookie' => Parsers::SetCookie,
|
155
|
+
'X-Content-Type-Options' => Parsers::XContentTypeOptions,
|
156
|
+
'X-Frame-Options' => Parsers::XFrameOptions,
|
157
|
+
'X-Permitted-Cross-Domain-Policies' => Parsers::XPermittedCrossDomainPolicies,
|
158
|
+
'X-Xss-Protection' => Parsers::XXSSProtection
|
159
|
+
}
|
160
|
+
|
161
|
+
# Header names and their corresponding classes
|
162
|
+
HEADERS = {
|
163
|
+
'Cache-Control' => Headers::CacheControl,
|
164
|
+
'Content-Security-Policy' => Headers::ContentSecurityPolicy,
|
165
|
+
'Content-Security-Policy-Report-Only' => Headers::ContentSecurityPolicyReportOnly,
|
166
|
+
'Expires' => nil,
|
167
|
+
'Pragma' => Headers::Pragma,
|
168
|
+
'Public-Key-Pins' => Headers::PublicKeyPins,
|
169
|
+
'Public-Key-Pins-Report-Only' => Headers::PublicKeyPinsReportOnly,
|
170
|
+
'Strict-Transport-Security' => Headers::StrictTransportSecurity,
|
171
|
+
'Set-Cookie' => Headers::SetCookie,
|
172
|
+
'X-Content-Type-Options' => Headers::XContentTypeOptions,
|
173
|
+
'X-Frame-Options' => Headers::XFrameOptions,
|
174
|
+
'X-Permitted-Cross-Domain-Policies' => Headers::XPermittedCrossDomainPolicies,
|
175
|
+
'X-Xss-Protection' => Headers::XXSSProtection
|
176
|
+
}
|
177
|
+
|
178
|
+
# Header names and their corresponding fields.
|
179
|
+
FIELDS = {
|
180
|
+
'Cache-Control' => :cache_control,
|
181
|
+
'Content-Security-Policy' => :content_security_policy,
|
182
|
+
'Content-Security-Policy-Report-Only' => :content_security_policy_report_only,
|
183
|
+
'Expires' => :expires,
|
184
|
+
'Pragma' => :pragma,
|
185
|
+
'Public-Key-Pins' => :public_key_pins,
|
186
|
+
'Public-Key-Pins-Report-Only' => :public_key_pins_report_only,
|
187
|
+
'Strict-Transport-Security' => :strict_transport_security,
|
188
|
+
'Set-Cookie' => :set_cookie,
|
189
|
+
'X-Content-Type-Options' => :x_content_type_options,
|
190
|
+
'X-Frame-Options' => :x_frame_options,
|
191
|
+
'X-Permitted-Cross-Domain-Policies' => :x_permitted_cross_domain_policies,
|
192
|
+
'X-Xss-Protection' => :x_xss_protection,
|
193
|
+
}
|
194
|
+
|
195
|
+
#
|
196
|
+
# Parses the HTTP security headers of a HTTP response.
|
197
|
+
#
|
198
|
+
# @param [#[]] response
|
199
|
+
# An HTTP response object. Must provide access to headers via the `#[]`
|
200
|
+
# method.
|
201
|
+
#
|
202
|
+
# @return [Response]
|
203
|
+
# The parsed response.
|
204
|
+
#
|
205
|
+
# @api public
|
206
|
+
#
|
207
|
+
def self.parse(response)
|
208
|
+
fields = {}
|
209
|
+
|
210
|
+
FIELDS.each do |header,field|
|
211
|
+
if (value = response[header])
|
212
|
+
fields[field] = begin
|
213
|
+
parse_header(header,value)
|
214
|
+
rescue Parslet::ParseFailed => error
|
215
|
+
MalformedHeader.new(value,error.cause)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
return new(fields)
|
221
|
+
end
|
222
|
+
|
223
|
+
#
|
224
|
+
# Parses the HTTP security headers of a HTTP response.
|
225
|
+
#
|
226
|
+
# @param [#[]] response
|
227
|
+
# An HTTP response object. Must provide access to headers via the `#[]`
|
228
|
+
# method.
|
229
|
+
#
|
230
|
+
# @return [Response]
|
231
|
+
#
|
232
|
+
# @raise [Parslet::ParseFailed]
|
233
|
+
# One of the headers was malformed.
|
234
|
+
#
|
235
|
+
# @api public
|
236
|
+
#
|
237
|
+
def self.parse!(response)
|
238
|
+
fields = {}
|
239
|
+
|
240
|
+
FIELDS.each do |name,field|
|
241
|
+
if (value = response[name])
|
242
|
+
fields[field] = parse_header(name,value)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
return new(fields)
|
247
|
+
end
|
248
|
+
|
249
|
+
#
|
250
|
+
# Parses an individual header.
|
251
|
+
#
|
252
|
+
# @param [String] name
|
253
|
+
# The header name.
|
254
|
+
#
|
255
|
+
# @param [String] value
|
256
|
+
# The raw value of the header.
|
257
|
+
#
|
258
|
+
# @return [Hash]
|
259
|
+
# The parsed header data.
|
260
|
+
#
|
261
|
+
# @raise [InvalidHeader]
|
262
|
+
# The header was malformed.
|
263
|
+
#
|
264
|
+
def self.parse_header(name,value)
|
265
|
+
parser = PARSERS.fetch(name)
|
266
|
+
value = begin
|
267
|
+
parser.parse(value)
|
268
|
+
rescue Parslet::ParseFailed => error
|
269
|
+
raise(InvalidHeader.new(error.message,error.cause))
|
270
|
+
end
|
271
|
+
|
272
|
+
if (header = HEADERS[name])
|
273
|
+
header.new(value)
|
274
|
+
else
|
275
|
+
value
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
#
|
280
|
+
# Accesses an arbitrary security header.
|
281
|
+
#
|
282
|
+
# @param [String] header
|
283
|
+
# The canonical header name.
|
284
|
+
#
|
285
|
+
# @return [Object, nil]
|
286
|
+
# The parsed header value.
|
287
|
+
#
|
288
|
+
def [](header)
|
289
|
+
field = FIELDS.fetch(header)
|
290
|
+
|
291
|
+
return instance_variable_get("@#{field}")
|
292
|
+
end
|
293
|
+
|
294
|
+
#
|
295
|
+
# Enumerates over the parsed security header values.
|
296
|
+
#
|
297
|
+
# @yield [name, value]
|
298
|
+
# The given block will be passed each header name and parsed value.
|
299
|
+
#
|
300
|
+
# @yieldparam [String] name
|
301
|
+
# The canonical header name.
|
302
|
+
#
|
303
|
+
# @yieldparam [Object] value
|
304
|
+
# A header class from {Headers}.
|
305
|
+
#
|
306
|
+
# @return [Enumerator]
|
307
|
+
# If no block was given, an enumerator will be returned.
|
308
|
+
#
|
309
|
+
def each
|
310
|
+
return enum_for(__method__) unless block_given?
|
311
|
+
|
312
|
+
FIELDS.each do |header,field|
|
313
|
+
if (value = self[header])
|
314
|
+
yield header, value
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
return self
|
319
|
+
end
|
320
|
+
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|