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