http-security 0.1.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.
- 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
|