cookiejar 0.3.2 → 0.3.4
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 +5 -5
- data/.gitignore +7 -0
- data/.rspec +2 -0
- data/.travis.yml +17 -0
- data/Gemfile +2 -0
- data/LICENSE +1 -1
- data/README.markdown +20 -1
- data/Rakefile +11 -12
- data/_config.yml +1 -0
- data/cookiejar.gemspec +28 -0
- data/lib/cookiejar/cookie.rb +57 -63
- data/lib/cookiejar/cookie_validation.rb +105 -100
- data/lib/cookiejar/jar.rb +51 -49
- data/lib/cookiejar/version.rb +4 -0
- data/lib/cookiejar.rb +2 -1
- data/spec/cookie_spec.rb +90 -90
- data/spec/cookie_validation_spec.rb +147 -155
- data/spec/jar_spec.rb +107 -110
- data/spec/spec_helper.rb +5 -0
- metadata +55 -22
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'cgi'
|
2
3
|
require 'uri'
|
4
|
+
|
3
5
|
module CookieJar
|
4
6
|
# Represents a set of cookie validation errors
|
5
7
|
class InvalidCookieError < StandardError
|
@@ -8,50 +10,50 @@ module CookieJar
|
|
8
10
|
|
9
11
|
# Create a new instance
|
10
12
|
# @param [String, Array<String>] the validation issue(s) encountered
|
11
|
-
def initialize
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
def initialize(message)
|
14
|
+
if message.is_a? Array
|
15
|
+
@messages = message
|
16
|
+
message = message.join ', '
|
17
|
+
else
|
18
|
+
@messages = [message]
|
19
|
+
end
|
20
|
+
super message
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
22
24
|
# Contains logic to parse and validate cookie headers
|
23
25
|
module CookieValidation
|
26
|
+
# REGEX cookie matching
|
24
27
|
module PATTERN
|
25
28
|
include URI::REGEXP::PATTERN
|
26
29
|
|
27
|
-
TOKEN = '[^(),\/<>@;:\\\"\[\]?={}\s]+'
|
28
|
-
VALUE1 =
|
29
|
-
IPADDR = "#{IPV4ADDR}|#{IPV6ADDR}"
|
30
|
-
BASE_HOSTNAME = "(?:#{DOMLABEL}\\.)(?:((?:(?:#{DOMLABEL}\\.)+(?:#{TOPLABEL}\\.?))|local))"
|
30
|
+
TOKEN = '[^(),\/<>@;:\\\"\[\]?={}\s]+'.freeze
|
31
|
+
VALUE1 = '([^;]*)'.freeze
|
32
|
+
IPADDR = "#{IPV4ADDR}|#{IPV6ADDR}".freeze
|
33
|
+
BASE_HOSTNAME = "(?:#{DOMLABEL}\\.)(?:((?:(?:#{DOMLABEL}\\.)+(?:#{TOPLABEL}\\.?))|local))".freeze
|
31
34
|
|
32
|
-
QUOTED_PAIR =
|
33
|
-
LWS =
|
35
|
+
QUOTED_PAIR = '\\\\[\\x00-\\x7F]'.freeze
|
36
|
+
LWS = '\\r\\n(?:[ \\t]+)'.freeze
|
34
37
|
# TEXT="[\\t\\x20-\\x7E\\x80-\\xFF]|(?:#{LWS})"
|
35
|
-
QDTEXT="[\\t\\x20-\\x21\\x23-\\x7E\\x80-\\xFF]|(?:#{LWS})"
|
36
|
-
QUOTED_TEXT = "\\\"(?:#{QDTEXT}|#{QUOTED_PAIR})*\\\""
|
37
|
-
VALUE2 = "#{TOKEN}|#{QUOTED_TEXT}"
|
38
|
-
|
38
|
+
QDTEXT = "[\\t\\x20-\\x21\\x23-\\x7E\\x80-\\xFF]|(?:#{LWS})".freeze
|
39
|
+
QUOTED_TEXT = "\\\"(?:#{QDTEXT}|#{QUOTED_PAIR})*\\\"".freeze
|
40
|
+
VALUE2 = "#{TOKEN}|#{QUOTED_TEXT}".freeze
|
39
41
|
end
|
40
42
|
BASE_HOSTNAME = /#{PATTERN::BASE_HOSTNAME}/
|
41
|
-
BASE_PATH =
|
43
|
+
BASE_PATH = %r{\A((?:[^/?#]*/)*)}
|
42
44
|
IPADDR = /\A#{PATTERN::IPV4ADDR}\Z|\A#{PATTERN::IPV6ADDR}\Z/
|
43
45
|
HDN = /\A#{PATTERN::HOSTNAME}\Z/
|
44
46
|
TOKEN = /\A#{PATTERN::TOKEN}\Z/
|
45
47
|
PARAM1 = /\A(#{PATTERN::TOKEN})(?:=#{PATTERN::VALUE1})?\Z/
|
46
|
-
PARAM2 = Regexp.new
|
48
|
+
PARAM2 = Regexp.new("(#{PATTERN::TOKEN})(?:=(#{PATTERN::VALUE2}))?(?:\\Z|;)", Regexp::NOENCODING)
|
47
49
|
# TWO_DOT_DOMAINS = /\A\.(com|edu|net|mil|gov|int|org)\Z/
|
48
50
|
|
49
51
|
# Converts the input object to a URI (if not already a URI)
|
50
52
|
#
|
51
53
|
# @param [String, URI] request_uri URI we are normalizing
|
52
54
|
# @param [URI] URI representation of input string, or original URI
|
53
|
-
def self.to_uri
|
54
|
-
(request_uri.is_a? URI)? request_uri : (URI.parse request_uri)
|
55
|
+
def self.to_uri(request_uri)
|
56
|
+
(request_uri.is_a? URI) ? request_uri : (URI.parse request_uri)
|
55
57
|
end
|
56
58
|
|
57
59
|
# Converts an input cookie or uri to a string representing the path.
|
@@ -59,7 +61,7 @@ module CookieJar
|
|
59
61
|
#
|
60
62
|
# @param [String, URI, Cookie] object containing the path
|
61
63
|
# @return [String] path information
|
62
|
-
def self.to_path
|
64
|
+
def self.to_path(uri_or_path)
|
63
65
|
if (uri_or_path.is_a? URI) || (uri_or_path.is_a? Cookie)
|
64
66
|
uri_or_path.path
|
65
67
|
else
|
@@ -72,7 +74,7 @@ module CookieJar
|
|
72
74
|
#
|
73
75
|
# @param [String, URI, Cookie] object containing the domain
|
74
76
|
# @return [String] domain information.
|
75
|
-
def self.to_domain
|
77
|
+
def self.to_domain(uri_or_domain)
|
76
78
|
if uri_or_domain.is_a? URI
|
77
79
|
uri_or_domain.host
|
78
80
|
elsif uri_or_domain.is_a? Cookie
|
@@ -88,10 +90,10 @@ module CookieJar
|
|
88
90
|
# @param [String] tested_domain domain to be tested against
|
89
91
|
# @param [String] base_domain new domain being tested
|
90
92
|
# @return [String,nil] matching domain on success, nil on failure
|
91
|
-
def self.domains_match
|
93
|
+
def self.domains_match(tested_domain, base_domain)
|
92
94
|
base = effective_host base_domain
|
93
95
|
search_domains = compute_search_domains_for_host base
|
94
|
-
|
96
|
+
search_domains.find do |domain|
|
95
97
|
domain == tested_domain
|
96
98
|
end
|
97
99
|
end
|
@@ -101,21 +103,19 @@ module CookieJar
|
|
101
103
|
#
|
102
104
|
# @param [String,URI,Cookie] hostname hostname, or object holding hostname
|
103
105
|
# @return [String,nil] next highest hostname, or nil if none
|
104
|
-
def self.hostname_reach
|
106
|
+
def self.hostname_reach(hostname)
|
105
107
|
host = to_domain hostname
|
106
108
|
host = host.downcase
|
107
109
|
match = BASE_HOSTNAME.match host
|
108
|
-
if match
|
109
|
-
match[1]
|
110
|
-
end
|
110
|
+
match[1] if match
|
111
111
|
end
|
112
112
|
|
113
113
|
# Compute the base of a path, for default cookie path assignment
|
114
114
|
#
|
115
115
|
# @param [String, URI, Cookie] path, or object holding path
|
116
116
|
# @return base path (all characters up to final '/')
|
117
|
-
def self.cookie_base_path
|
118
|
-
BASE_PATH.match(to_path
|
117
|
+
def self.cookie_base_path(path)
|
118
|
+
BASE_PATH.match(to_path(path))[1]
|
119
119
|
end
|
120
120
|
|
121
121
|
# Processes cookie path data using the following rules:
|
@@ -123,16 +123,16 @@ module CookieJar
|
|
123
123
|
# to the last '/' character. If no path is specified in the cookie, a path
|
124
124
|
# value will be taken from the request URI which was used for the site.
|
125
125
|
#
|
126
|
-
# Note that this will not attempt to detect a mismatch of the request uri
|
127
|
-
# and explicitly specified cookie path
|
126
|
+
# Note that this will not attempt to detect a mismatch of the request uri
|
127
|
+
# domain and explicitly specified cookie path
|
128
128
|
#
|
129
129
|
# @param [String,URI] request URI yielding this cookie
|
130
130
|
# @param [String] path on cookie
|
131
|
-
def self.determine_cookie_path
|
131
|
+
def self.determine_cookie_path(request_uri, cookie_path)
|
132
132
|
uri = to_uri request_uri
|
133
133
|
cookie_path = to_path cookie_path
|
134
134
|
|
135
|
-
if cookie_path
|
135
|
+
if cookie_path.nil? || cookie_path.empty?
|
136
136
|
cookie_path = cookie_base_path uri.path
|
137
137
|
end
|
138
138
|
cookie_path
|
@@ -145,8 +145,9 @@ module CookieJar
|
|
145
145
|
# @param [String, URI] request_uri requested uri
|
146
146
|
# @return [Array<String>] all cookie domain values which would match the
|
147
147
|
# requested uri
|
148
|
-
def self.compute_search_domains
|
148
|
+
def self.compute_search_domains(request_uri)
|
149
149
|
uri = to_uri request_uri
|
150
|
+
return nil unless uri.is_a? URI::HTTP
|
150
151
|
host = uri.host
|
151
152
|
compute_search_domains_for_host host
|
152
153
|
end
|
@@ -157,59 +158,55 @@ module CookieJar
|
|
157
158
|
# @param [String] host host being requested
|
158
159
|
# @return [Array<String>] all cookie domain values which would match the
|
159
160
|
# requested uri
|
160
|
-
def self.compute_search_domains_for_host
|
161
|
+
def self.compute_search_domains_for_host(host)
|
161
162
|
host = effective_host host
|
162
163
|
result = [host]
|
163
164
|
unless host =~ IPADDR
|
164
165
|
result << ".#{host}"
|
165
166
|
base = hostname_reach host
|
166
|
-
if base
|
167
|
-
result << ".#{base}"
|
168
|
-
end
|
167
|
+
result << ".#{base}" if base
|
169
168
|
end
|
170
169
|
result
|
171
170
|
end
|
172
171
|
|
173
172
|
# Processes cookie domain data using the following rules:
|
174
173
|
# Domains strings of the form .foo.com match 'foo.com' and all immediate
|
175
|
-
# subdomains of 'foo.com'. Domain strings specified of the form 'foo.com'
|
176
|
-
# modified to '.foo.com', and as such will still apply to subdomains.
|
174
|
+
# subdomains of 'foo.com'. Domain strings specified of the form 'foo.com'
|
175
|
+
# are modified to '.foo.com', and as such will still apply to subdomains.
|
177
176
|
#
|
178
|
-
# Cookies without an explicit domain will have their domain value taken
|
179
|
-
# from the URL, and will _NOT_ have any leading dot applied. For
|
180
|
-
# to http://foo.com/ will cause an entry for 'foo.com'
|
181
|
-
# to foo.com but no subdomain.
|
177
|
+
# Cookies without an explicit domain will have their domain value taken
|
178
|
+
# directly from the URL, and will _NOT_ have any leading dot applied. For
|
179
|
+
# example, a request to http://foo.com/ will cause an entry for 'foo.com'
|
180
|
+
# to be created - which applies to foo.com but no subdomain.
|
182
181
|
#
|
183
|
-
# Note that this will not attempt to detect a mismatch of the request uri
|
184
|
-
# and explicitly specified cookie domain
|
182
|
+
# Note that this will not attempt to detect a mismatch of the request uri
|
183
|
+
# domain and explicitly specified cookie domain
|
185
184
|
#
|
186
185
|
# @param [String, URI] request_uri originally requested URI
|
187
186
|
# @param [String] cookie domain value
|
188
187
|
# @return [String] effective host
|
189
|
-
def self.determine_cookie_domain
|
188
|
+
def self.determine_cookie_domain(request_uri, cookie_domain)
|
190
189
|
uri = to_uri request_uri
|
191
190
|
domain = to_domain cookie_domain
|
192
191
|
|
193
|
-
if domain
|
194
|
-
|
192
|
+
return effective_host(uri.host) if domain.nil? || domain.empty?
|
193
|
+
domain = domain.downcase
|
194
|
+
if domain =~ IPADDR || domain.start_with?('.')
|
195
|
+
domain
|
195
196
|
else
|
196
|
-
domain
|
197
|
-
if domain =~ IPADDR || domain.start_with?('.')
|
198
|
-
domain
|
199
|
-
else
|
200
|
-
".#{domain}"
|
201
|
-
end
|
197
|
+
".#{domain}"
|
202
198
|
end
|
203
199
|
end
|
204
200
|
|
205
201
|
# Compute the effective host (RFC 2965, section 1)
|
206
202
|
#
|
207
|
-
# Has the added additional logic of searching for interior dots
|
208
|
-
# matches colons to prevent .local being suffixed on
|
203
|
+
# Has the added additional logic of searching for interior dots
|
204
|
+
# specifically, and matches colons to prevent .local being suffixed on
|
205
|
+
# IPv6 addresses
|
209
206
|
#
|
210
207
|
# @param [String, URI] host_or_uridomain name, or absolute URI
|
211
208
|
# @return [String] effective host per RFC rules
|
212
|
-
def self.effective_host
|
209
|
+
def self.effective_host(host_or_uri)
|
213
210
|
hostname = to_domain host_or_uri
|
214
211
|
hostname = hostname.downcase
|
215
212
|
|
@@ -227,11 +224,9 @@ module CookieJar
|
|
227
224
|
# @param [Cookie] cookie object
|
228
225
|
# @param [true] will always return true on success
|
229
226
|
# @raise [InvalidCookieError] on failures, containing all validation errors
|
230
|
-
def self.validate_cookie
|
227
|
+
def self.validate_cookie(request_uri, cookie)
|
231
228
|
uri = to_uri request_uri
|
232
|
-
request_host = effective_host uri.host
|
233
229
|
request_path = uri.path
|
234
|
-
request_secure = (uri.scheme == 'https')
|
235
230
|
cookie_host = cookie.domain
|
236
231
|
cookie_path = cookie.path
|
237
232
|
|
@@ -242,19 +237,22 @@ module CookieJar
|
|
242
237
|
# A user agent rejects (SHALL NOT store its information) if the
|
243
238
|
# Version attribute is missing. Note that the legacy Set-Cookie
|
244
239
|
# directive will result in an implicit version 0.
|
245
|
-
unless cookie.version
|
246
|
-
errors << "Version missing"
|
247
|
-
end
|
240
|
+
errors << 'Version missing' unless cookie.version
|
248
241
|
|
249
242
|
# The value for the Path attribute is not a prefix of the request-URI
|
243
|
+
|
244
|
+
# If the initial request path is empty then this will always fail
|
245
|
+
# so check if it is empty and if so then set it to /
|
246
|
+
request_path = '/' if request_path == ''
|
247
|
+
|
250
248
|
unless request_path.start_with? cookie_path
|
251
|
-
errors <<
|
249
|
+
errors << 'Path is not a prefix of the request uri path'
|
252
250
|
end
|
253
251
|
|
254
|
-
unless cookie_host =~ IPADDR || #is an IPv4 or IPv6 address
|
255
|
-
|
256
|
-
|
257
|
-
errors <<
|
252
|
+
unless cookie_host =~ IPADDR || # is an IPv4 or IPv6 address
|
253
|
+
cookie_host =~ /.\../ || # contains an embedded dot
|
254
|
+
cookie_host == '.local' # is the domain cookie for local addresses
|
255
|
+
errors << 'Domain format is illegal'
|
258
256
|
end
|
259
257
|
|
260
258
|
# The effective host name that derives from the request-host does
|
@@ -264,18 +262,18 @@ module CookieJar
|
|
264
262
|
# where D is the value of the Domain attribute, and H is a string
|
265
263
|
# that contains one or more dots.
|
266
264
|
unless domains_match cookie_host, uri
|
267
|
-
errors <<
|
265
|
+
errors << 'Domain is inappropriate based on request URI hostname'
|
268
266
|
end
|
269
267
|
|
270
268
|
# The Port attribute has a "port-list", and the request-port was
|
271
269
|
# not in the list.
|
272
|
-
unless cookie.ports.nil? || cookie.ports.
|
270
|
+
unless cookie.ports.nil? || !cookie.ports.empty?
|
273
271
|
unless cookie.ports.find_index uri.port
|
274
|
-
errors <<
|
272
|
+
errors << 'Ports list does not contain request URI port'
|
275
273
|
end
|
276
274
|
end
|
277
275
|
|
278
|
-
|
276
|
+
fail InvalidCookieError, errors unless errors.empty?
|
279
277
|
|
280
278
|
# Note: 'secure' is not explicitly defined as an SSL channel, and no
|
281
279
|
# test is defined around validity and the 'secure' attribute
|
@@ -289,15 +287,16 @@ module CookieJar
|
|
289
287
|
# @param [String] set_cookie_value a Set-Cookie header formatted cookie
|
290
288
|
# definition
|
291
289
|
# @return [Hash] Contains the parsed values of the cookie
|
292
|
-
def self.parse_set_cookie
|
293
|
-
args = {
|
294
|
-
params=set_cookie_value.split(/;\s*/)
|
290
|
+
def self.parse_set_cookie(set_cookie_value)
|
291
|
+
args = {}
|
292
|
+
params = set_cookie_value.split(/;\s*/)
|
295
293
|
|
296
|
-
first=true
|
294
|
+
first = true
|
297
295
|
params.each do |param|
|
298
296
|
result = PARAM1.match param
|
299
|
-
|
300
|
-
|
297
|
+
unless result
|
298
|
+
fail InvalidCookieError,
|
299
|
+
"Invalid cookie parameter in cookie '#{set_cookie_value}'"
|
301
300
|
end
|
302
301
|
key = result[1].downcase.to_sym
|
303
302
|
keyvalue = result[2]
|
@@ -311,17 +310,21 @@ module CookieJar
|
|
311
310
|
begin
|
312
311
|
args[:expires_at] = Time.parse keyvalue
|
313
312
|
rescue ArgumentError
|
314
|
-
raise unless
|
313
|
+
raise unless $ERROR_INFO.message == 'time out of range'
|
315
314
|
args[:expires_at] = Time.at(0x7FFFFFFF)
|
316
315
|
end
|
317
|
-
when
|
316
|
+
when :"max-age"
|
317
|
+
args[:max_age] = keyvalue.to_i
|
318
|
+
when :domain, :path
|
318
319
|
args[key] = keyvalue
|
319
320
|
when :secure
|
320
321
|
args[:secure] = true
|
321
322
|
when :httponly
|
322
323
|
args[:http_only] = true
|
324
|
+
when :samesite
|
325
|
+
args[:samesite] = keyvalue.downcase
|
323
326
|
else
|
324
|
-
|
327
|
+
fail InvalidCookieError, "Unknown cookie parameter '#{key}'"
|
325
328
|
end
|
326
329
|
end
|
327
330
|
end
|
@@ -330,18 +333,18 @@ module CookieJar
|
|
330
333
|
end
|
331
334
|
|
332
335
|
# Parse a RFC 2965 value and convert to a literal string
|
333
|
-
def self.value_to_string
|
334
|
-
if /\A"(.*)"\Z
|
335
|
-
value =
|
336
|
-
value
|
336
|
+
def self.value_to_string(value)
|
337
|
+
if /\A"(.*)"\Z/ =~ value
|
338
|
+
value = Regexp.last_match(1)
|
339
|
+
value.gsub(/\\(.)/, '\1')
|
337
340
|
else
|
338
341
|
value
|
339
342
|
end
|
340
343
|
end
|
341
344
|
|
342
345
|
# Attempt to decipher a partially decoded version of text cookie values
|
343
|
-
def self.decode_value
|
344
|
-
if /\A"(.*)"\Z
|
346
|
+
def self.decode_value(value)
|
347
|
+
if /\A"(.*)"\Z/ =~ value
|
345
348
|
value_to_string value
|
346
349
|
else
|
347
350
|
CGI.unescape value
|
@@ -355,16 +358,17 @@ module CookieJar
|
|
355
358
|
# @param [String] set_cookie_value a Set-Cookie2 header formatted cookie
|
356
359
|
# definition
|
357
360
|
# @return [Hash] Contains the parsed values of the cookie
|
358
|
-
def self.parse_set_cookie2
|
359
|
-
args = {
|
361
|
+
def self.parse_set_cookie2(set_cookie_value)
|
362
|
+
args = {}
|
360
363
|
first = true
|
361
364
|
index = 0
|
362
365
|
begin
|
363
366
|
md = PARAM2.match set_cookie_value[index..-1]
|
364
367
|
if md.nil? || md.offset(0).first != 0
|
365
|
-
|
368
|
+
fail InvalidCookieError,
|
369
|
+
"Invalid Set-Cookie2 header '#{set_cookie_value}'"
|
366
370
|
end
|
367
|
-
index+=md.offset(0)[1]
|
371
|
+
index += md.offset(0)[1]
|
368
372
|
|
369
373
|
key = md[1].downcase.to_sym
|
370
374
|
keyvalue = md[2] || md[3]
|
@@ -375,9 +379,9 @@ module CookieJar
|
|
375
379
|
else
|
376
380
|
keyvalue = value_to_string keyvalue
|
377
381
|
case key
|
378
|
-
when
|
382
|
+
when :comment, :commenturl, :domain, :path
|
379
383
|
args[key] = keyvalue
|
380
|
-
when
|
384
|
+
when :discard, :secure
|
381
385
|
args[key] = true
|
382
386
|
when :httponly
|
383
387
|
args[:http_only] = true
|
@@ -388,15 +392,16 @@ module CookieJar
|
|
388
392
|
when :port
|
389
393
|
# must be in format '"port,port"'
|
390
394
|
ports = keyvalue.split(/,\s*/)
|
391
|
-
args[:ports] = ports.map
|
395
|
+
args[:ports] = ports.map(&:to_i)
|
392
396
|
else
|
393
|
-
|
397
|
+
fail InvalidCookieError, "Unknown cookie parameter '#{key}'"
|
394
398
|
end
|
395
399
|
end
|
396
400
|
end until md.post_match.empty?
|
397
401
|
# if our last match in the scan failed
|
398
402
|
if args[:version] != 1
|
399
|
-
|
403
|
+
fail InvalidCookieError,
|
404
|
+
'Set-Cookie2 declares a non RFC2965 version cookie'
|
400
405
|
end
|
401
406
|
|
402
407
|
args
|
data/lib/cookiejar/jar.rb
CHANGED
@@ -39,7 +39,7 @@ module CookieJar
|
|
39
39
|
#
|
40
40
|
# Paths are given a straight prefix string comparison to match.
|
41
41
|
# Further filters <secure, http only, ports> are not represented in this
|
42
|
-
#
|
42
|
+
# hierarchy.
|
43
43
|
#
|
44
44
|
# Cookies returned are ordered solely by specificity (length) of the
|
45
45
|
# path.
|
@@ -56,7 +56,7 @@ module CookieJar
|
|
56
56
|
# @param [String] cookie_header_value the contents of the Set-Cookie
|
57
57
|
# @return [Cookie] which was created and stored
|
58
58
|
# @raise [InvalidCookieError] if the cookie header did not validate
|
59
|
-
def set_cookie
|
59
|
+
def set_cookie(request_uri, cookie_header_values)
|
60
60
|
cookie_header_values.split(/, (?=[\w]+=)/).each do |cookie_header_value|
|
61
61
|
cookie = Cookie.from_set_cookie request_uri, cookie_header_value
|
62
62
|
add_cookie cookie
|
@@ -70,7 +70,7 @@ module CookieJar
|
|
70
70
|
# @param [String] cookie_header_value the contents of the Set-Cookie2
|
71
71
|
# @return [Cookie] which was created and stored
|
72
72
|
# @raise [InvalidCookieError] if the cookie header did not validate
|
73
|
-
def set_cookie2
|
73
|
+
def set_cookie2(request_uri, cookie_header_value)
|
74
74
|
cookie = Cookie.from_set_cookie2 request_uri, cookie_header_value
|
75
75
|
add_cookie cookie
|
76
76
|
end
|
@@ -87,7 +87,7 @@ module CookieJar
|
|
87
87
|
# @return [Array<Cookie>,nil] the cookies created, or nil if none found.
|
88
88
|
# @raise [InvalidCookieError] if one of the cookie headers contained
|
89
89
|
# invalid formatting or data
|
90
|
-
def set_cookies_from_headers
|
90
|
+
def set_cookies_from_headers(request_uri, http_headers)
|
91
91
|
set_cookie_key = http_headers.keys.detect { |k| /\ASet-Cookie\Z/i.match k }
|
92
92
|
cookies = gather_header_values http_headers[set_cookie_key] do |value|
|
93
93
|
begin
|
@@ -123,7 +123,7 @@ module CookieJar
|
|
123
123
|
#
|
124
124
|
# @param [Cookie] cookie a pre-existing cookie object
|
125
125
|
# @return [Cookie] the cookie added to the store
|
126
|
-
def add_cookie
|
126
|
+
def add_cookie(cookie)
|
127
127
|
domain_paths = find_or_add_domain_for_cookie cookie
|
128
128
|
add_cookie_to_path domain_paths, cookie
|
129
129
|
cookie
|
@@ -149,7 +149,7 @@ module CookieJar
|
|
149
149
|
# @param [Array] a options controlling output JSON text
|
150
150
|
# (usually a State and a depth)
|
151
151
|
# @return [String] JSON representation of object data
|
152
|
-
def to_json
|
152
|
+
def to_json(*a)
|
153
153
|
{
|
154
154
|
'json_class' => self.class.name,
|
155
155
|
'cookies' => to_a.to_json(*a)
|
@@ -160,17 +160,13 @@ module CookieJar
|
|
160
160
|
#
|
161
161
|
# @param o [Hash] the expanded JSON object
|
162
162
|
# @return [CookieJar] a new CookieJar instance
|
163
|
-
def self.json_create
|
164
|
-
if o.is_a? String
|
165
|
-
|
166
|
-
end
|
167
|
-
if o.is_a? Hash
|
168
|
-
o = o['cookies']
|
169
|
-
end
|
163
|
+
def self.json_create(o)
|
164
|
+
o = JSON.parse(o) if o.is_a? String
|
165
|
+
o = o['cookies'] if o.is_a? Hash
|
170
166
|
cookies = o.inject([]) do |result, cookie_json|
|
171
167
|
result << (Cookie.json_create cookie_json)
|
172
168
|
end
|
173
|
-
|
169
|
+
from_a cookies
|
174
170
|
end
|
175
171
|
|
176
172
|
# Create a new Jar from an array of Cookie objects. Expired cookies
|
@@ -179,7 +175,7 @@ module CookieJar
|
|
179
175
|
#
|
180
176
|
# @param [Array<Cookie>] cookies array of cookie objects
|
181
177
|
# @return [CookieJar] a new CookieJar instance
|
182
|
-
def self.from_a
|
178
|
+
def self.from_a(cookies)
|
183
179
|
jar = new
|
184
180
|
cookies.each do |cookie|
|
185
181
|
jar.add_cookie cookie
|
@@ -192,10 +188,10 @@ module CookieJar
|
|
192
188
|
#
|
193
189
|
# @param session [Boolean] whether session cookies should be expired,
|
194
190
|
# or just cookies past their expiration date.
|
195
|
-
def expire_cookies
|
196
|
-
@domains.delete_if do |
|
197
|
-
paths.delete_if do |
|
198
|
-
cookies.delete_if do |
|
191
|
+
def expire_cookies(session = false)
|
192
|
+
@domains.delete_if do |_domain, paths|
|
193
|
+
paths.delete_if do |_path, cookies|
|
194
|
+
cookies.delete_if do |_cookie_name, cookie|
|
199
195
|
cookie.expired? || (session && cookie.session?)
|
200
196
|
end
|
201
197
|
cookies.empty?
|
@@ -209,27 +205,36 @@ module CookieJar
|
|
209
205
|
# otherwise unordered.
|
210
206
|
#
|
211
207
|
# @param [String, URI] request_uri the address the HTTP request will be
|
212
|
-
# sent to
|
208
|
+
# sent to. This must be a full URI, i.e. must include the protocol,
|
209
|
+
# if you pass digi.ninja it will fail to find the domain, you must pass
|
210
|
+
# http://digi.ninja
|
213
211
|
# @param [Hash] opts options controlling returned cookies
|
214
|
-
# @option opts [Boolean] :script (false) Cookies marked HTTP-only will be
|
215
|
-
# if true
|
212
|
+
# @option opts [Boolean] :script (false) Cookies marked HTTP-only will be
|
213
|
+
# ignored if true
|
216
214
|
# @return [Array<Cookie>] cookies which should be sent in the HTTP request
|
217
|
-
def get_cookies
|
215
|
+
def get_cookies(request_uri, opts = {})
|
218
216
|
uri = to_uri request_uri
|
219
217
|
hosts = Cookie.compute_search_domains uri
|
220
218
|
|
219
|
+
return [] if hosts.nil?
|
220
|
+
|
221
|
+
path = if uri.path == ''
|
222
|
+
'/'
|
223
|
+
else
|
224
|
+
uri.path
|
225
|
+
end
|
226
|
+
|
221
227
|
results = []
|
222
228
|
hosts.each do |host|
|
223
229
|
domain = find_domain host
|
224
|
-
domain.each do |
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
end
|
230
|
+
domain.each do |apath, cookies|
|
231
|
+
next unless path.start_with? apath
|
232
|
+
results += cookies.values.select do |cookie|
|
233
|
+
cookie.should_send? uri, opts[:script]
|
229
234
|
end
|
230
235
|
end
|
231
236
|
end
|
232
|
-
#Sort by path length, longest first
|
237
|
+
# Sort by path length, longest first
|
233
238
|
results.sort do |lhs, rhs|
|
234
239
|
rhs.path.length <=> lhs.path.length
|
235
240
|
end
|
@@ -242,22 +247,19 @@ module CookieJar
|
|
242
247
|
# @param [String, URI] request_uri the address the HTTP request will be
|
243
248
|
# sent to
|
244
249
|
# @param [Hash] opts options controlling returned cookies
|
245
|
-
# @option opts [Boolean] :script (false) Cookies marked HTTP-only will be
|
246
|
-
# if true
|
250
|
+
# @option opts [Boolean] :script (false) Cookies marked HTTP-only will be
|
251
|
+
# ignored if true
|
247
252
|
# @return String value of the Cookie header which should be sent on the
|
248
253
|
# HTTP request
|
249
|
-
def get_cookie_header
|
254
|
+
def get_cookie_header(request_uri, opts = {})
|
250
255
|
cookies = get_cookies request_uri, opts
|
251
|
-
|
252
|
-
ver = [[],[]]
|
256
|
+
ver = [[], []]
|
253
257
|
cookies.each do |cookie|
|
254
258
|
ver[cookie.version] << cookie
|
255
259
|
end
|
256
|
-
if
|
260
|
+
if ver[1].empty?
|
257
261
|
# can do a netscape-style cookie header, relish the opportunity
|
258
|
-
cookies.map
|
259
|
-
cookie.to_s
|
260
|
-
end.join ";"
|
262
|
+
cookies.map(&:to_s).join ';'
|
261
263
|
else
|
262
264
|
# build a RFC 2965-style cookie header. Split the cookies into
|
263
265
|
# version 0 and 1 groups so that we can reuse the '$Version' header
|
@@ -265,46 +267,46 @@ module CookieJar
|
|
265
267
|
unless ver[0].empty?
|
266
268
|
result << '$Version=0;'
|
267
269
|
result << ver[0].map do |cookie|
|
268
|
-
(cookie.to_s 1,false)
|
270
|
+
(cookie.to_s 1, false)
|
269
271
|
end.join(';')
|
270
272
|
# separate version 0 and 1 with a comma
|
271
273
|
result << ','
|
272
274
|
end
|
273
275
|
result << '$Version=1;'
|
274
276
|
ver[1].map do |cookie|
|
275
|
-
result << (cookie.to_s 1,false)
|
277
|
+
result << (cookie.to_s 1, false)
|
276
278
|
end
|
277
279
|
result
|
278
280
|
end
|
279
281
|
end
|
280
282
|
|
281
|
-
|
283
|
+
protected
|
282
284
|
|
283
|
-
def gather_header_values
|
285
|
+
def gather_header_values(http_header_value, &_block)
|
284
286
|
result = []
|
285
287
|
if http_header_value.is_a? Array
|
286
288
|
http_header_value.each do |value|
|
287
|
-
result <<
|
289
|
+
result << yield(value)
|
288
290
|
end
|
289
291
|
elsif http_header_value.is_a? String
|
290
|
-
result <<
|
292
|
+
result << yield(http_header_value)
|
291
293
|
end
|
292
294
|
result.compact
|
293
295
|
end
|
294
296
|
|
295
|
-
def to_uri
|
296
|
-
(request_uri.is_a? URI)? request_uri : (URI.parse request_uri)
|
297
|
+
def to_uri(request_uri)
|
298
|
+
(request_uri.is_a? URI) ? request_uri : (URI.parse request_uri)
|
297
299
|
end
|
298
300
|
|
299
|
-
def find_domain
|
301
|
+
def find_domain(host)
|
300
302
|
@domains[host] || {}
|
301
303
|
end
|
302
304
|
|
303
|
-
def find_or_add_domain_for_cookie
|
305
|
+
def find_or_add_domain_for_cookie(cookie)
|
304
306
|
@domains[cookie.domain] ||= {}
|
305
307
|
end
|
306
308
|
|
307
|
-
def add_cookie_to_path
|
309
|
+
def add_cookie_to_path(paths, cookie)
|
308
310
|
path_entry = (paths[cookie.path] ||= {})
|
309
311
|
path_entry[cookie.name] = cookie
|
310
312
|
end
|