http-security 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +21 -0
  5. data/.yardopts +1 -0
  6. data/ChangeLog.md +17 -0
  7. data/Gemfile +17 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.md +90 -0
  10. data/Rakefile +34 -0
  11. data/http-security.gemspec +23 -0
  12. data/lib/http/security.rb +2 -0
  13. data/lib/http/security/exceptions.rb +8 -0
  14. data/lib/http/security/headers.rb +12 -0
  15. data/lib/http/security/headers/cache_control.rb +36 -0
  16. data/lib/http/security/headers/content_security_policy.rb +71 -0
  17. data/lib/http/security/headers/content_security_policy_report_only.rb +10 -0
  18. data/lib/http/security/headers/pragma.rb +24 -0
  19. data/lib/http/security/headers/public_key_pins.rb +60 -0
  20. data/lib/http/security/headers/public_key_pins_report_only.rb +10 -0
  21. data/lib/http/security/headers/set_cookie.rb +75 -0
  22. data/lib/http/security/headers/strict_transport_security.rb +29 -0
  23. data/lib/http/security/headers/x_content_type_options.rb +24 -0
  24. data/lib/http/security/headers/x_frame_options.rb +39 -0
  25. data/lib/http/security/headers/x_permitted_cross_domain_policies.rb +47 -0
  26. data/lib/http/security/headers/x_xss_protection.rb +34 -0
  27. data/lib/http/security/http_date.rb +13 -0
  28. data/lib/http/security/malformed_header.rb +33 -0
  29. data/lib/http/security/parsers.rb +14 -0
  30. data/lib/http/security/parsers/cache_control.rb +62 -0
  31. data/lib/http/security/parsers/content_security_policy.rb +128 -0
  32. data/lib/http/security/parsers/content_security_policy_report_only.rb +10 -0
  33. data/lib/http/security/parsers/expires.rb +19 -0
  34. data/lib/http/security/parsers/parser.rb +408 -0
  35. data/lib/http/security/parsers/pragma.rb +25 -0
  36. data/lib/http/security/parsers/public_key_pins.rb +43 -0
  37. data/lib/http/security/parsers/public_key_pins_report_only.rb +10 -0
  38. data/lib/http/security/parsers/set_cookie.rb +62 -0
  39. data/lib/http/security/parsers/strict_transport_security.rb +42 -0
  40. data/lib/http/security/parsers/x_content_type_options.rb +19 -0
  41. data/lib/http/security/parsers/x_frame_options.rb +47 -0
  42. data/lib/http/security/parsers/x_permitted_cross_domain_policies.rb +33 -0
  43. data/lib/http/security/parsers/x_xss_protection.rb +27 -0
  44. data/lib/http/security/response.rb +323 -0
  45. data/lib/http/security/version.rb +5 -0
  46. data/spec/data/alexa.csv +100 -0
  47. data/spec/headers/cache_control_spec.rb +40 -0
  48. data/spec/headers/content_security_policy_spec.rb +46 -0
  49. data/spec/headers/pragma_spec.rb +26 -0
  50. data/spec/headers/public_key_pins_spec.rb +68 -0
  51. data/spec/headers/set_cookie_spec.rb +122 -0
  52. data/spec/headers/strict_transport_security_spec.rb +39 -0
  53. data/spec/headers/x_content_type_options_spec.rb +26 -0
  54. data/spec/headers/x_frame_options_spec.rb +86 -0
  55. data/spec/headers/x_permitted_cross_domain_policies_spec.rb +108 -0
  56. data/spec/headers/x_xss_protection_spec.rb +59 -0
  57. data/spec/parsers/cache_control_spec.rb +26 -0
  58. data/spec/parsers/content_security_policy_report_only_spec.rb +48 -0
  59. data/spec/parsers/content_security_policy_spec.rb +74 -0
  60. data/spec/parsers/expires_spec.rb +71 -0
  61. data/spec/parsers/parser_spec.rb +317 -0
  62. data/spec/parsers/pragma_spec.rb +10 -0
  63. data/spec/parsers/public_key_pins_spec.rb +81 -0
  64. data/spec/parsers/set_cookie_spec.rb +55 -0
  65. data/spec/parsers/strict_transport_security_spec.rb +62 -0
  66. data/spec/parsers/x_content_type_options_spec.rb +10 -0
  67. data/spec/parsers/x_frame_options_spec.rb +24 -0
  68. data/spec/parsers/x_permitted_cross_domain_policies_spec.rb +34 -0
  69. data/spec/parsers/x_xss_protection_spec.rb +39 -0
  70. data/spec/response_spec.rb +262 -0
  71. data/spec/spec_helper.rb +13 -0
  72. data/tasks/alexa.rb +40 -0
  73. 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,10 @@
1
+ require 'http/security/parsers/public_key_pins'
2
+
3
+ module HTTP
4
+ module Security
5
+ module Parsers
6
+ class PublicKeyPinsReportOnly < PublicKeyPins
7
+ end
8
+ end
9
+ end
10
+ 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