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,62 @@
1
+ require "spec_helper"
2
+ require "http/security/parsers/strict_transport_security"
3
+
4
+ describe Parsers::StrictTransportSecurity do
5
+ it "accepts only max-age" do
6
+ header = "max-age=31536000"
7
+
8
+ expect(subject.parse(header)).to be == {max_age: 31536000}
9
+ end
10
+
11
+ it "accepts max-age of zero" do
12
+ header = "max-age=0"
13
+
14
+ expect(subject.parse(header)).to eq(max_age: 0)
15
+ end
16
+
17
+ it "accepts max-age then includeSubDomains" do
18
+ header = "max-age=0; includeSubDomains"
19
+
20
+ expect(subject.parse(header)).to be == {
21
+ max_age: 0,
22
+ includesubdomains: true
23
+ }
24
+ end
25
+
26
+ it "accepts includeSubDomains then max-age" do
27
+ header = "includeSubDomains; max-age=0"
28
+
29
+ expect(subject.parse(header)).to be == {
30
+ includesubdomains: true,
31
+ max_age: 0
32
+ }
33
+ end
34
+
35
+ describe "stp_header_extension" do
36
+ subject { super().stp_header_extension }
37
+
38
+ it "accepts includedSubdomains" do
39
+ expect(subject.parse('includeSubDomains')).to be == {
40
+ key: "includeSubDomains"
41
+ }
42
+ end
43
+
44
+ it "accepts an unsupported token" do
45
+ expect(subject.parse("preload")).to be == {name: 'preload'}
46
+ end
47
+
48
+ it "accepts an unsupported token=token" do
49
+ expect(subject.parse("foo=bar")).to be == {
50
+ name: 'foo',
51
+ value: 'bar'
52
+ }
53
+ end
54
+
55
+ it "accepts token=\"string\"" do
56
+ expect(subject.parse('foo="string"')).to be == {
57
+ name: 'foo',
58
+ value: {string: 'string'}
59
+ }
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,10 @@
1
+ require "spec_helper"
2
+ require "http/security/parsers/x_content_type_options"
3
+
4
+ describe Parsers::XContentTypeOptions do
5
+ it "accepts nosniff" do
6
+ header = "nosniff"
7
+
8
+ expect(subject.parse(header)).to eq(nosniff: true)
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ require "spec_helper"
2
+ require "http/security/parsers/x_frame_options"
3
+
4
+ describe Parsers::XFrameOptions do
5
+ it "parses deny" do
6
+ header = "deny"
7
+
8
+ expect(subject.parse(header)).to be == {deny: true}
9
+ end
10
+
11
+ it "parses allow-from" do
12
+ header = "allow-from http://www.example.com"
13
+
14
+ expect(subject.parse(header)).to be == {
15
+ allow_from: URI("http://www.example.com")
16
+ }
17
+ end
18
+
19
+ it "parses sameorigin" do
20
+ header = "sameorigin"
21
+
22
+ expect(subject.parse(header)).to be == {sameorigin: true}
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ require "spec_helper"
2
+ require "http/security/parsers/x_permitted_cross_domain_policies"
3
+
4
+ describe Parsers::XPermittedCrossDomainPolicies do
5
+ it "accepts none" do
6
+ header = "none"
7
+
8
+ expect(subject.parse(header)).to be == {none: true}
9
+ end
10
+
11
+ it "accepts master-only" do
12
+ header = "master-only"
13
+
14
+ expect(subject.parse(header)).to be == {master_only: true}
15
+ end
16
+
17
+ it "accepts by-content-type" do
18
+ header = "by-content-type"
19
+
20
+ expect(subject.parse(header)).to be == {by_content_type: true}
21
+ end
22
+
23
+ it "accepts by-ftp-filename" do
24
+ header = "by-ftp-filename"
25
+
26
+ expect(subject.parse(header)).to be == {by_ftp_filename: true}
27
+ end
28
+
29
+ it "accepts all" do
30
+ header = "all"
31
+
32
+ expect(subject.parse(header)).to be == {all: true}
33
+ end
34
+ end
@@ -0,0 +1,39 @@
1
+ require "spec_helper"
2
+ require "http/security/parsers/x_xss_protection"
3
+
4
+ describe Parsers::XXSSProtection do
5
+ it "it accepts 1" do
6
+ header = "1"
7
+
8
+ expect(subject.parse(header)).to eq(enabled: true)
9
+ end
10
+
11
+ it "it accepts 0" do
12
+ header = "0"
13
+
14
+ expect(subject.parse(header)).to eq(enabled: false)
15
+ end
16
+
17
+ it "it accepts 1; mode=block" do
18
+ header = "1; mode=block"
19
+
20
+ expect(subject.parse(header)).to eq(enabled: true, mode: 'block')
21
+ end
22
+
23
+ it "it accepts 0; mode=block" do
24
+ header = "0; mode=block"
25
+
26
+ expect(subject.parse(header)).to eq(enabled: false, mode: 'block')
27
+ end
28
+
29
+ it "it accepts 1; mode=block; report=..." do
30
+ report_uri = "/xss-report/25b8988e-64ff-45a8-b0c6-2700fc1e9abd?source%5Baction%5D=index&source%5Bcontroller%5D=shop&source%5Bsection%5D=storefront"
31
+ header = "1; mode=block; report=#{report_uri}"
32
+
33
+ expect(subject.parse(header)).to eq(
34
+ enabled: true,
35
+ mode: 'block',
36
+ report: report_uri
37
+ )
38
+ end
39
+ end
@@ -0,0 +1,262 @@
1
+ require "spec_helper"
2
+ require "http/security/response"
3
+
4
+ describe Response do
5
+ subject { described_class }
6
+
7
+ let(:response) do
8
+ {
9
+ "Cache-Control" => "no-cache, no-store, must-revalidate, pre-check=0, post-check=0",
10
+ "Content-Length" => "12682",
11
+ "Content-Security-Policy" => "default-src https:; connect-src https:; font-src https: data:; frame-src https: twitter:; img-src https: data:; media-src https:; object-src https:; script-src 'unsafe-inline' 'unsafe-eval' https:; style-src 'unsafe-inline' https:; report-uri https://twitter.com/i/csp_report?a=NVQWGYLXFVZXO2LGOQ%3D%3D%3D%3D%3D%3D&ro=false;",
12
+ "Content-Security-Policy-Report-Only" => "default-src https:; connect-src https:; font-src https: data:; frame-src https: twitter:; img-src https: data:; media-src https:; object-src https:; script-src 'unsafe-inline' 'unsafe-eval' https:; style-src 'unsafe-inline' https:; report-uri https://twitter.com/i/csp_report?a=NVQWGYLXFVZXO2LGOQ%3D%3D%3D%3D%3D%3D&ro=false;",
13
+ "Content-Type" => "text/html;charset=utf-8",
14
+ "Date" => "Thu, 20 Nov 2014 00:27:36 UTC",
15
+ "Expires" => "Tue, 31 Mar 1981 05:00:00 GMT",
16
+ "Last-Modified" => "Thu, 20 Nov 2014 00:27:36 GMT",
17
+ "Ms" => "A",
18
+ "Pragma" => "no-cache",
19
+ "Public-Key-Pins" => "pin-sha256=\"j+rQEAhMMJvg6xmn0rzlpe4WZgr7dz9tc7bVhUTsY4E=\"; pin-sha256=\"klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=\"; pin-sha256=\"6X0iNAQtPIjXKEVcqZBwyMcRwq1yW60549axatu3oDE=\"; max-age=0; includeSubDomains",
20
+ "Public-Key-Pins-Report-Only" => "pin-sha256=\"j+rQEAhMMJvg6xmn0rzlpe4WZgr7dz9tc7bVhUTsY4E=\"; pin-sha256=\"klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=\"; pin-sha256=\"6X0iNAQtPIjXKEVcqZBwyMcRwq1yW60549axatu3oDE=\"; max-age=0; includeSubDomains",
21
+ "Server" => "tsa_b",
22
+ "Set-Cookie" =>
23
+ "_twitter_sess=BAh7CSIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%250ASGFzaHsABjoKQHVzZWR7ADoPY3JlYXRlZF9hdGwrCOzcmMpJAToMY3NyZl9p%250AZCIlYmEzNTQ5YzM0MzYwZjAzZWMwMTFmZDY3MzVhMjE0MzM6B2lkIiUxMzI3%250AY2M1OWIyYzM3N2IzMmYxZWZiNmJlN2ZmYzdjZQ%253D%253D--09c51d06332d2b4cf102948a3f0491131ed952fa; Path=/; Domain=.twitter.com; Secure; HTTPOnly, guest_id=v1%3A141644325604142464; Domain=.twitter.com; Path=/; Expires=Sat, 19 Nov 2016 00:27:36 GMT",
24
+ "Status" => "200 OK",
25
+ "Strict-Transport-Security" => "max-age=631138519",
26
+ "X-Connection-Hash" => "f58cf3aa568cfd2abfd6a259c85a453b",
27
+ "X-Content-Type-Options" => "nosniff",
28
+ "X-Frame-Options" => "SAMEORIGIN",
29
+ "X-Transaction" => "a0c1a67d4d799176",
30
+ "X-Permitted-Cross-Domain-Policies" => "none",
31
+ "X-Ua-Compatible" => "IE=edge,chrome=1",
32
+ "X-Xss-Protection" => "1; mode=block"
33
+ }
34
+ end
35
+
36
+ describe ".parse_header" do
37
+ subject { described_class }
38
+
39
+ context "when parsing a valid header" do
40
+ let(:name) { 'Set-Cookie' }
41
+ let(:value) { "_twitter_sess=BAh7CSIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%250ASGFzaHsABjoKQHVzZWR7ADoPY3JlYXRlZF9hdGwrCOzcmMpJAToMY3NyZl9p%250AZCIlYmEzNTQ5YzM0MzYwZjAzZWMwMTFmZDY3MzVhMjE0MzM6B2lkIiUxMzI3%250AY2M1OWIyYzM3N2IzMmYxZWZiNmJlN2ZmYzdjZQ%253D%253D--09c51d06332d2b4cf102948a3f0491131ed952fa; Path=/; Domain=.twitter.com; Secure; HTTPOnly, guest_id=v1%3A141644325604142464; Domain=.twitter.com; Path=/; Expires=Sat, 19 Nov 2016 00:27:36 GMT" }
42
+
43
+ subject { super().parse_header(name,value) }
44
+
45
+ it "should parse the given header" do
46
+ expect(subject).to be_kind_of(Headers::SetCookie)
47
+
48
+ expect(subject.cookies[0].name).to be == :_twitter_sess
49
+ expect(subject.cookies[0].value).to be == "BAh7CSIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%250ASGFzaHsABjoKQHVzZWR7ADoPY3JlYXRlZF9hdGwrCOzcmMpJAToMY3NyZl9p%250AZCIlYmEzNTQ5YzM0MzYwZjAzZWMwMTFmZDY3MzVhMjE0MzM6B2lkIiUxMzI3%250AY2M1OWIyYzM3N2IzMmYxZWZiNmJlN2ZmYzdjZQ%253D%253D--09c51d06332d2b4cf102948a3f0491131ed952fa"
50
+ expect(subject.cookies[0].path).to be == '/'
51
+ expect(subject.cookies[0].domain).to be == '.twitter.com'
52
+ expect(subject.cookies[0]).to be_secure
53
+ expect(subject.cookies[0]).to be_http_only
54
+
55
+ expect(subject.cookies[1].name).to be == :guest_id
56
+ expect(subject.cookies[1].value).to be == 'v1%3A141644325604142464'
57
+ expect(subject.cookies[1].domain).to be == '.twitter.com'
58
+ expect(subject.cookies[1].path).to be == '/'
59
+ expect(subject.cookies[1].expires).to be == HTTPDate.parse('Sat, 19 Nov 2016 00:27:36 GMT')
60
+ end
61
+ end
62
+
63
+ context "when parsing an invalid header" do
64
+ let(:name) { 'Set-Cookie' }
65
+ let(:value) { "foo" }
66
+
67
+ it "should raise an InvalidHeader exception" do
68
+ expect {
69
+ subject.parse_header(name,value)
70
+ }.to raise_error(InvalidHeader)
71
+ end
72
+ end
73
+ end
74
+
75
+ describe ".parse" do
76
+ subject { described_class.parse(response) }
77
+
78
+ it "should parse Cache-Control" do
79
+ expect(subject.cache_control).to be_kind_of(Headers::CacheControl)
80
+ end
81
+
82
+ it "should parse Content-Security-Policy" do
83
+ expect(subject.content_security_policy).to be_kind_of(Headers::ContentSecurityPolicy)
84
+ end
85
+
86
+ it "should parse Content-Security-Policy-Report-Only" do
87
+ expect(subject.content_security_policy_report_only).to be_kind_of(Headers::ContentSecurityPolicyReportOnly)
88
+ end
89
+
90
+ it "should parse Expires" do
91
+ expect(subject.expires).to be_kind_of(HTTPDate)
92
+ end
93
+
94
+ it "should parse Pragma" do
95
+ expect(subject.pragma).to be_kind_of(Headers::Pragma)
96
+ end
97
+
98
+ it "should parse Public-Key-Pins" do
99
+ expect(subject.public_key_pins).to be_kind_of(Headers::PublicKeyPins)
100
+ end
101
+
102
+ it "should parse Public-Key-Pins-Report-Only" do
103
+ expect(subject.public_key_pins_report_only).to be_kind_of(Headers::PublicKeyPinsReportOnly)
104
+ end
105
+
106
+ it "should parse Set-Cookie" do
107
+ expect(subject.set_cookie).to be_kind_of(Headers::SetCookie)
108
+ end
109
+
110
+ it "should parse Strict-Transport-Security" do
111
+ expect(subject.strict_transport_security).to be_kind_of(Headers::StrictTransportSecurity)
112
+ end
113
+
114
+ it "should parse X-Content-Type-Options" do
115
+ expect(subject.x_content_type_options).to be_kind_of(Headers::XContentTypeOptions)
116
+ end
117
+
118
+ it "should parse X-Frame-Options" do
119
+ expect(subject.x_frame_options).to be_kind_of(Headers::XFrameOptions)
120
+ end
121
+
122
+ it "should parse X-Permitted-Cross-Domain-Policies" do
123
+ expect(subject.x_permitted_cross_domain_policies).to be_kind_of(Headers::XPermittedCrossDomainPolicies)
124
+ end
125
+
126
+ it "should parse X-XSS-Protection" do
127
+ expect(subject.x_xss_protection).to be_kind_of(Headers::XXSSProtection)
128
+ end
129
+
130
+ context "when parsing a malformed headers" do
131
+ let(:response) do
132
+ {
133
+ "Date"=>"Sat, 24 Jan 2015 01:06:57 GMT",
134
+ "X-Servedby"=>"ny1-prod6-web022.int.peer1.squarespace.net",
135
+ "Set-Cookie"=>
136
+ "JSESSIONID=3bGO1nRYPl_-0xhNbk8nlpt-5ulKdHw9_ofbsM_wsJ-Wcul2ODOLqQ;Path=/;HttpOnly, crumb=ce3ba8cfc3;Path=/, SS_MID=0763cce4-5adf-4332-a545-f5b4ebefd803i5aarqtx;Path=/;Domain=.singularads.com;Expires=Tue, 21-Jan-2025 01:06:57 GMT",
137
+ "Expires"=>"Thu, 01 Jan 1970 00:00:00 GMT",
138
+ "Accept-Ranges"=>"bytes",
139
+ "Content-Type"=>"text/html; charset=UTF-8",
140
+ "X-Pc-Appver"=>"3070",
141
+ "Content-Encoding"=>"gzip",
142
+ "X-Pc-Date"=>"Fri, 23 Jan 2015 21:00:13 GMT",
143
+ "X-Pc-Host"=>"10.100.101.11",
144
+ "Etag"=>"W/\"0eaeb395a0f1a569174a067510085a66\"",
145
+ "X-Pc-Key"=>"m7ZrcPWNTQjYc6jL7aW2oRSLPko-daniel-matalon-vim3",
146
+ "X-Pc-Hit"=>"true",
147
+ "Content-Length"=>"27469",
148
+ "X-Contextid"=>"nLOTffVs/ksjmZDEy",
149
+ "X-Via"=>"1.1 ny1-prod-echo016.int.peer1.squarespace.net"
150
+ }
151
+ end
152
+
153
+ it "should map exceptions to MalformedHeader objects" do
154
+ expect(subject.set_cookie).to be_kind_of(MalformedHeader)
155
+ end
156
+ end
157
+
158
+ context "Alexa 100", :gauntlet do
159
+ require 'csv'
160
+ require 'net/http'
161
+
162
+ path = File.expand_path('../data/alexa.csv', __FILE__)
163
+ csv = CSV.new(open(path), headers: false)
164
+
165
+ csv.each do |row|
166
+ rank, domain = row[0].to_i, row[1].downcase
167
+
168
+ context domain do
169
+ it "should not raise a ParseError" do
170
+ begin
171
+ response = Net::HTTP.get_response(URI("http://#{domain}/"))
172
+
173
+ expect {
174
+ described_class.parse(response)
175
+ }.to_not raise_error(Parslet::ParseError)
176
+ rescue => error
177
+ pending error.message
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ describe ".parse!" do
186
+ let(:response) do
187
+ {
188
+ "Date"=>"Sat, 24 Jan 2015 01:06:57 GMT",
189
+ "X-Servedby"=>"ny1-prod6-web022.int.peer1.squarespace.net",
190
+ "Set-Cookie"=>
191
+ "JSESSIONID=3bGO1nRYPl_-0xhNbk8nlpt-5ulKdHw9_ofbsM_wsJ-Wcul2ODOLqQ;Path=/;HttpOnly, crumb=ce3ba8cfc3;Path=/, SS_MID=0763cce4-5adf-4332-a545-f5b4ebefd803i5aarqtx;Path=/;Domain=.singularads.com;Expires=Tue, 21-Jan-2025 01:06:57 GMT",
192
+ "Expires"=>"Thu, 01 Jan 1970 00:00:00 GMT",
193
+ "Accept-Ranges"=>"bytes",
194
+ "Content-Type"=>"text/html; charset=UTF-8",
195
+ "X-Pc-Appver"=>"3070",
196
+ "Content-Encoding"=>"gzip",
197
+ "X-Pc-Date"=>"Fri, 23 Jan 2015 21:00:13 GMT",
198
+ "X-Pc-Host"=>"10.100.101.11",
199
+ "Etag"=>"W/\"0eaeb395a0f1a569174a067510085a66\"",
200
+ "X-Pc-Key"=>"m7ZrcPWNTQjYc6jL7aW2oRSLPko-daniel-matalon-vim3",
201
+ "X-Pc-Hit"=>"true",
202
+ "Content-Length"=>"27469",
203
+ "X-Contextid"=>"nLOTffVs/ksjmZDEy",
204
+ "X-Via"=>"1.1 ny1-prod-echo016.int.peer1.squarespace.net"
205
+ }
206
+ end
207
+
208
+ subject { described_class }
209
+
210
+ context "when parsing malformed headers" do
211
+ it "should raise a Parslet::ParseFailed exception" do
212
+ expect {
213
+ subject.parse!(response)
214
+ }.to raise_error(Parslet::ParseFailed)
215
+ end
216
+ end
217
+ end
218
+
219
+ describe "#[]" do
220
+ subject { described_class.parse(response) }
221
+
222
+ it "should retrieve a single header value" do
223
+ expect(subject['Cache-Control']).to be(subject.cache_control)
224
+ end
225
+
226
+ context "when given an unknown header name" do
227
+ it "should raise a KeyError" do
228
+ expect { expect(subject['Foo-Bar']) }.to raise_error(KeyError)
229
+ end
230
+ end
231
+ end
232
+
233
+ describe "#each" do
234
+ subject { described_class.parse(response) }
235
+
236
+ context "when given a block" do
237
+ it "should yield each header name and parsed value" do
238
+ expect { |b| subject.each(&b) }.to yield_successive_args(
239
+ ['Cache-Control', subject.cache_control],
240
+ ['Content-Security-Policy', subject.content_security_policy],
241
+ ['Content-Security-Policy-Report-Only', subject.content_security_policy_report_only],
242
+ ['Expires', subject.expires],
243
+ ['Pragma', subject.pragma],
244
+ ['Public-Key-Pins', subject.public_key_pins],
245
+ ['Public-Key-Pins-Report-Only', subject.public_key_pins_report_only],
246
+ ['Strict-Transport-Security', subject.strict_transport_security],
247
+ ['Set-Cookie', subject.set_cookie],
248
+ ['X-Content-Type-Options', subject.x_content_type_options],
249
+ ['X-Frame-Options', subject.x_frame_options],
250
+ ['X-Permitted-Cross-Domain-Policies', subject.x_permitted_cross_domain_policies],
251
+ ['X-Xss-Protection', subject.x_xss_protection]
252
+ )
253
+ end
254
+ end
255
+
256
+ context "when no block is given" do
257
+ it "should return an Enumerator" do
258
+ expect(subject.each).to be_kind_of(Enumerator)
259
+ end
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,13 @@
1
+ if ENV['CODECLIMATE_REPO_TOKEN']
2
+ require "codeclimate-test-reporter"
3
+ CodeClimate::TestReporter.start
4
+ end
5
+
6
+ require 'rspec'
7
+ require 'http/security/version'
8
+
9
+ include HTTP::Security
10
+
11
+ RSpec.configure do |specs|
12
+ specs.filter_run_excluding :gauntlet
13
+ end