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.
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