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,317 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require 'http/security/parsers/parser'
|
3
|
+
|
4
|
+
describe Parsers::Parser do
|
5
|
+
describe "#http_date" do
|
6
|
+
subject { super().http_date }
|
7
|
+
|
8
|
+
it "parses rfc1123-date" do
|
9
|
+
date = "Thu, 04 Dec 2015 16:00:00 GMT"
|
10
|
+
|
11
|
+
expect(subject.parse(date)).to eq(date: date)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "parses rfc850-date" do
|
15
|
+
date = "Thursday, 04-Dec-15 16:00:00 GMT"
|
16
|
+
|
17
|
+
expect(subject.parse(date)).to eq(date: date)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "parses rfc1123-date" do
|
21
|
+
date = "Thu Dec 04 16:00:00 2015"
|
22
|
+
|
23
|
+
expect(subject.parse(date)).to eq(date: date)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#header_extension" do
|
28
|
+
subject { super().header_extension }
|
29
|
+
|
30
|
+
let(:name) { 'foo' }
|
31
|
+
|
32
|
+
context "when parsing a token" do
|
33
|
+
it "should tag the token name" do
|
34
|
+
expect(subject.parse(name)).to eq(name: name)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "when parsing a token and a value" do
|
39
|
+
let(:value) { 'bar' }
|
40
|
+
|
41
|
+
it "should tag the token and value" do
|
42
|
+
expect(subject.parse("#{name}=#{value}")).to eq(
|
43
|
+
name: name,
|
44
|
+
value: value
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#uri" do
|
51
|
+
let(:transform) { Parsers::Parser::Transform.new }
|
52
|
+
subject { super().uri }
|
53
|
+
|
54
|
+
it "parses a uri without a scheme specified" do
|
55
|
+
uri = "www.example.com"
|
56
|
+
expect(transform.apply(subject.parse(uri))).to eq(URI.parse(uri))
|
57
|
+
end
|
58
|
+
|
59
|
+
it "parses a uri with a scheme specified" do
|
60
|
+
uri = "https://www.example.com"
|
61
|
+
expect(transform.apply(subject.parse(uri))).to eq(URI.parse(uri))
|
62
|
+
end
|
63
|
+
|
64
|
+
it "parses a uri with a path specified" do
|
65
|
+
uri = "http://www.example.com/about"
|
66
|
+
expect(transform.apply(subject.parse(uri))).to eq(URI.parse(uri))
|
67
|
+
end
|
68
|
+
|
69
|
+
it "parses a uri with parameters" do
|
70
|
+
uri = "http://www.example.com/about?parameter1=val1¶meter2=val2"
|
71
|
+
expect(transform.apply(subject.parse(uri))).to eq(URI.parse(uri))
|
72
|
+
end
|
73
|
+
|
74
|
+
it "parses a uri with a redirect address in its parameters" do
|
75
|
+
uri = "http://www.example.com/url?sa=X&q=http://example2.com/article/headline_20101013&ct=ga&cad=:n1:n2:t1286988171:&cd=yQ=AG-Tx"
|
76
|
+
expect(transform.apply(subject.parse(uri))).to eq(URI.parse(uri))
|
77
|
+
end
|
78
|
+
|
79
|
+
it "parses a uri with fragments" do
|
80
|
+
uri = "http://www.example.com/url#fragment"
|
81
|
+
expect(transform.apply(subject.parse(uri))).to eq(URI.parse(uri))
|
82
|
+
end
|
83
|
+
|
84
|
+
it "parses an ftp uri" do
|
85
|
+
uri = "ftp://ftp.is.co.za/rfc/rfc1808.txt"
|
86
|
+
expect(transform.apply(subject.parse(uri))).to eq(URI.parse(uri))
|
87
|
+
end
|
88
|
+
|
89
|
+
it "parses a uri with a port" do
|
90
|
+
uri = "telnet://192.0.2.16:80/"
|
91
|
+
expect(transform.apply(subject.parse(uri))).to eq(URI.parse(uri))
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe described_class::Transform do
|
96
|
+
describe "boolean" do
|
97
|
+
it "should map '0' to false" do
|
98
|
+
expect(subject.apply({boolean: '0'})).to be false
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should map 'no' to false" do
|
102
|
+
expect(subject.apply({boolean: 'no'})).to be false
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should map 'false' to false" do
|
106
|
+
expect(subject.apply({boolean: 'false'})).to be false
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should map '1' to true" do
|
110
|
+
expect(subject.apply({boolean: '1'})).to be true
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should map 'yes' to true" do
|
114
|
+
expect(subject.apply({boolean: 'yes'})).to be true
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should map 'true' to false" do
|
118
|
+
expect(subject.apply({boolean: 'true'})).to be true
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "numeric" do
|
123
|
+
it "should coerce Strings to Integer values" do
|
124
|
+
expect(subject.apply({numeric: '42'})).to be 42
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "escaped_char" do
|
129
|
+
context "when the escaped char is a control character" do
|
130
|
+
let(:char) { 'n' }
|
131
|
+
|
132
|
+
it "should map it to the control character" do
|
133
|
+
expect(subject.apply({escaped_char: 'n'})).to be == "\n"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context "when the escaped char is a printable character" do
|
138
|
+
let(:char) { 'x' }
|
139
|
+
|
140
|
+
it "should return the printable character" do
|
141
|
+
expect(subject.apply({escaped_char: char})).to be == char
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe "string" do
|
147
|
+
context "when given one String" do
|
148
|
+
let(:string) { "foo bar" }
|
149
|
+
|
150
|
+
it "should return the String" do
|
151
|
+
expect(subject.apply({string: string})).to be == string
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context "when given multiple Strings" do
|
156
|
+
let(:strings) { ['foo', "\n", 'bar'] }
|
157
|
+
|
158
|
+
it "should join the Strings" do
|
159
|
+
expect(subject.apply({string: strings})).to be == strings.join
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
describe "date" do
|
165
|
+
let(:string) { 'Tue, 24 Mar 2015 00:00:00 GMT' }
|
166
|
+
let(:date) { Date.parse(string) }
|
167
|
+
|
168
|
+
it "should return an HTTPDate" do
|
169
|
+
expect(subject.apply({date: string})).to be_kind_of(HTTPDate)
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should coerce Strings to Date values" do
|
173
|
+
expect(subject.apply({date: string})).to be == date
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
describe "uri" do
|
178
|
+
let(:string) { 'https://www.example.com/?foo=bar' }
|
179
|
+
let(:uri) { URI(string) }
|
180
|
+
|
181
|
+
it "should parse Strings as URIs" do
|
182
|
+
expect(subject.apply({uri: string})).to be == uri
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "list" do
|
187
|
+
context "when given a single element" do
|
188
|
+
let(:element) { 'foo' }
|
189
|
+
|
190
|
+
it "should return an Array of the element" do
|
191
|
+
expect(subject.apply({list: element})).to be == [element]
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context "when given multiple elements" do
|
196
|
+
let(:elements) { %w[foo bar baz] }
|
197
|
+
|
198
|
+
it "should return the elements" do
|
199
|
+
expect(subject.apply({list: elements})).to be == elements
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe "key" do
|
205
|
+
let(:name) { 'foo' }
|
206
|
+
let(:key) { :foo }
|
207
|
+
|
208
|
+
it "should return a Hash with the name and true" do
|
209
|
+
expect(subject.apply({key: name})).to be == {key => true}
|
210
|
+
end
|
211
|
+
|
212
|
+
context "when given mixed-case String" do
|
213
|
+
let(:name) { 'fooBar' }
|
214
|
+
let(:key) { :foobar }
|
215
|
+
|
216
|
+
it "should downcase the String" do
|
217
|
+
expect(subject.apply({key: name})).to be == {key => true}
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context "when given a hyphenated String" do
|
222
|
+
let(:name) { 'foo-bar' }
|
223
|
+
let(:key) { :foo_bar }
|
224
|
+
|
225
|
+
it "should replace the hyphens with underscores" do
|
226
|
+
expect(subject.apply({key: name})).to be == {key => true}
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe "key with simple value" do
|
232
|
+
let(:name) { 'foo' }
|
233
|
+
let(:key) { :foo }
|
234
|
+
let(:value) { 'bar' }
|
235
|
+
|
236
|
+
it "should return a Hash of the name and value" do
|
237
|
+
expect(subject.apply({key: name, value: value})).to be == {
|
238
|
+
key => value
|
239
|
+
}
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
describe "key with values" do
|
244
|
+
let(:name) { 'foo' }
|
245
|
+
let(:key) { :foo }
|
246
|
+
let(:value) { {'x' => 1} }
|
247
|
+
|
248
|
+
it "should return a Hash of the name and value" do
|
249
|
+
expect(subject.apply({key: name, values: value})).to be == {
|
250
|
+
key => value
|
251
|
+
}
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
describe "name" do
|
256
|
+
let(:name) { 'foo' }
|
257
|
+
|
258
|
+
it "should return a Hash with the name and true" do
|
259
|
+
expect(subject.apply({name: name})).to be == {name => true}
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
describe "name with simple value" do
|
264
|
+
let(:name) { 'foo' }
|
265
|
+
let(:value) { 'bar' }
|
266
|
+
|
267
|
+
it "should return a Hash of the name and value" do
|
268
|
+
expect(subject.apply({name: name, value: value})).to be == {
|
269
|
+
name => value
|
270
|
+
}
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
describe "name with a tree of values" do
|
275
|
+
let(:name) { 'foo' }
|
276
|
+
let(:tree) { {'x' => 1} }
|
277
|
+
|
278
|
+
it "should return a Hash of the name and values" do
|
279
|
+
expect(subject.apply({name: name, values: tree})).to be == {
|
280
|
+
name => tree
|
281
|
+
}
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
describe "directives" do
|
286
|
+
context "when given a single Hash" do
|
287
|
+
let(:hash) { {foo: 'bar'} }
|
288
|
+
|
289
|
+
it "should return the Hash" do
|
290
|
+
expect(subject.apply({directives: hash})).to be == hash
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
context "when given multiple Hashes" do
|
295
|
+
let(:hash1) { {foo: 'bar'} }
|
296
|
+
let(:hash2) { {baz: 'quix'} }
|
297
|
+
let(:hashes) { [hash1, hash2] }
|
298
|
+
let(:hash) { hash1.merge(hash2) }
|
299
|
+
|
300
|
+
it "should return a merged Hash" do
|
301
|
+
expect(subject.apply({directives: hashes})).to be == hash
|
302
|
+
end
|
303
|
+
|
304
|
+
context "when the Hashes share key names" do
|
305
|
+
let(:hash1) { {foo: '1', bar: '2'} }
|
306
|
+
let(:hash2) { {bar: '3', baz: '4'} }
|
307
|
+
let(:hashes) { [hash1, hash2] }
|
308
|
+
let(:hash) { {foo: '1', bar: ['2', '3'], baz: '4'} }
|
309
|
+
|
310
|
+
it "should combine the values into an Array" do
|
311
|
+
expect(subject.apply({directives: hashes})).to be == hash
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'http/security/parsers/public_key_pins'
|
3
|
+
|
4
|
+
describe Parsers::PublicKeyPins do
|
5
|
+
let(:pin_sha256) { 'klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=' }
|
6
|
+
let(:header) { "pin-sha256=\"#{pin_sha256}\"" }
|
7
|
+
|
8
|
+
it "parses the one pin-sha256=..." do
|
9
|
+
expect(subject.parse(header)).to be == {pin_sha256: pin_sha256}
|
10
|
+
end
|
11
|
+
|
12
|
+
context "when given multiple pin-sha256= directives" do
|
13
|
+
let(:primary) { pin_sha256 }
|
14
|
+
let(:secondary) { 'M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE=' }
|
15
|
+
let(:header) { "pin-sha256=\"#{primary}\"; pin-sha256=\"#{secondary}\"" }
|
16
|
+
|
17
|
+
it "parses the pin-sha256=... directives into an Array" do
|
18
|
+
expect(subject.parse(header)).to be == {pin_sha256: [primary, secondary]}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "when given pin- directives with unsupported hash algorithms" do
|
23
|
+
let(:pin_sha9000) { "foo" }
|
24
|
+
let(:header) { "#{super()}; pin-sha9000=\"#{pin_sha9000}\"" }
|
25
|
+
|
26
|
+
it "parses the unsupported pin- directives" do
|
27
|
+
expect(subject.parse(header)).to be == {
|
28
|
+
pin_sha256: pin_sha256,
|
29
|
+
'pin-sha9000' => pin_sha9000
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "when the max-age= directive is present" do
|
35
|
+
let(:max_age) { 31536000 }
|
36
|
+
let(:header) { "pin-sha256=\"#{pin_sha256}\"; max-age=#{max_age}" }
|
37
|
+
|
38
|
+
it "accepts pin-sha256=...; max-age=..." do
|
39
|
+
expect(subject.parse(header)).to be == {
|
40
|
+
pin_sha256: pin_sha256,
|
41
|
+
max_age: max_age
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when the includeSubdomains directive is present" do
|
46
|
+
let(:header) { "#{super()}; includeSubdomains" }
|
47
|
+
|
48
|
+
it "accepts pin-sha256=...; max-age=...; includeSubdomains" do
|
49
|
+
expect(subject.parse(header)).to be == {
|
50
|
+
pin_sha256: pin_sha256,
|
51
|
+
max_age: max_age,
|
52
|
+
includesubdomains: true
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "when the report-uri directive is present" do
|
58
|
+
let(:report_uri) { URI('https://www.example.com/') }
|
59
|
+
let(:header) { "#{super()}; report-uri=\"#{report_uri}\"" }
|
60
|
+
|
61
|
+
it "accepts pin-sha256=...; max-age=...; includeSubdomains" do
|
62
|
+
expect(subject.parse(header)).to be == {
|
63
|
+
pin_sha256: pin_sha256,
|
64
|
+
max_age: max_age,
|
65
|
+
report_uri: report_uri
|
66
|
+
}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "when the strict directive is present" do
|
72
|
+
let(:header) { "#{super()}; strict" }
|
73
|
+
|
74
|
+
it "accepts pin-sha256=...; strict" do
|
75
|
+
expect(subject.parse(header)).to be == {
|
76
|
+
pin_sha256: pin_sha256,
|
77
|
+
strict: true
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "http/security/parsers/set_cookie"
|
3
|
+
|
4
|
+
describe Parsers::SetCookie do
|
5
|
+
it "accepts 'name=value'" do
|
6
|
+
expect(subject.parse('foo=bar')).to be == [{cookie: {foo: 'bar'}}]
|
7
|
+
end
|
8
|
+
|
9
|
+
it "accepts 'name=value; Expires=...'" do
|
10
|
+
expires = "Wed, 09 Jun 2021 10:18:14 GMT"
|
11
|
+
|
12
|
+
expect(subject.parse("foo=bar; Expires=#{expires}")).to be == [{
|
13
|
+
cookie: {foo: 'bar'},
|
14
|
+
expires: Date.parse(expires)
|
15
|
+
}]
|
16
|
+
end
|
17
|
+
|
18
|
+
it "accepts 'name=value; Path=...; Expires=...; Secure; Domain=...; HttpOnly'" do
|
19
|
+
path = '/accounts'
|
20
|
+
expires = "Wed, 09 Jun 2021 10:18:14 GMT"
|
21
|
+
domain = '.example.com'
|
22
|
+
|
23
|
+
expect(subject.parse("foo=bar; Path=#{path}; Expires=#{expires}; Secure; Domain=#{domain}; HttpOnly")).to be == [{
|
24
|
+
cookie: {foo: 'bar'},
|
25
|
+
path: path,
|
26
|
+
expires: Date.parse(expires),
|
27
|
+
secure: 'Secure',
|
28
|
+
domain: domain,
|
29
|
+
http_only: 'HttpOnly'
|
30
|
+
}]
|
31
|
+
end
|
32
|
+
|
33
|
+
it "accepts multiple cookie values" do
|
34
|
+
path = '/'
|
35
|
+
domain = '.twitter.com'
|
36
|
+
expires = 'Sat, 19 Nov 2016 00:27:36 GMT'
|
37
|
+
|
38
|
+
expect(subject.parse("foo=bar; Path=#{path}; Domain=#{domain}; Secure; HTTPOnly, bar=baz; Domain=#{domain}; Path=#{path}; Expires=#{expires}")).to be == [
|
39
|
+
{
|
40
|
+
cookie: {foo: 'bar'},
|
41
|
+
path: path,
|
42
|
+
domain: domain,
|
43
|
+
secure: 'Secure',
|
44
|
+
http_only: 'HTTPOnly'
|
45
|
+
},
|
46
|
+
|
47
|
+
{
|
48
|
+
cookie: {bar: 'baz'},
|
49
|
+
domain: domain,
|
50
|
+
path: path,
|
51
|
+
expires: Date.parse(expires)
|
52
|
+
}
|
53
|
+
]
|
54
|
+
end
|
55
|
+
end
|