dwaite-cookiejar 0.1.1 → 0.1.2
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.
- data/lib/cookiejar/cookie.rb +136 -15
- data/lib/cookiejar/jar.rb +9 -14
- data/lib/cookiejar.rb +1 -0
- data/test/cookie_test.rb +157 -2
- metadata +3 -4
- data/lib/cookiejar/cookie_logic.rb +0 -129
- data/test/cookie_logic_test.rb +0 -157
data/lib/cookiejar/cookie.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'time'
|
2
|
-
require '
|
2
|
+
require 'uri'
|
3
|
+
require 'cookiejar/cookie_common'
|
3
4
|
|
4
5
|
module CookieJar
|
5
6
|
|
@@ -9,9 +10,29 @@ module CookieJar
|
|
9
10
|
# Specifically, the 'domain' and 'path' values may be set to defaults
|
10
11
|
# based on the requested resource that resulted in the cookie being set.
|
11
12
|
class Cookie
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
module PATTERN
|
14
|
+
include URI::REGEXP::PATTERN
|
15
|
+
|
16
|
+
TOKEN = '[^(),\/<>@;:\\\"\[\]?={}\s]*'
|
17
|
+
VALUE1 = "([^;]*)"
|
18
|
+
IPADDR = "#{IPV4ADDR}|#{IPV6ADDR}"
|
19
|
+
BASE_HOSTNAME = "(?:#{DOMLABEL}\\.)(?:((?:(?:#{DOMLABEL}\\.)+(?:#{TOPLABEL}\\.?))|local))"
|
20
|
+
|
21
|
+
# QUOTED_PAIR = "\\\\[\\x00-\\x7F]"
|
22
|
+
# LWS = "\\r\\n(?:[ \\t]+)"
|
23
|
+
# TEXT="[\\t\\x20-\\x7E\\x80-\\xFF]|(?:#{LWS})"
|
24
|
+
# QDTEXT="[\\t\\x20-\\x21\\x23-\\x7E\\x80-\\xFF]|(?:#{LWS})"
|
25
|
+
# QUOTED_TEXT = "\\\"((?:#{QDTEXT}|#{QUOTED_PAIR})*)\\\""
|
26
|
+
# VALUE2 = "(#{TOKEN})|#{QUOTED_TEXT}"
|
27
|
+
|
28
|
+
end
|
29
|
+
BASE_HOSTNAME = /#{PATTERN::BASE_HOSTNAME}/
|
30
|
+
BASE_PATH = /\A((?:[^\/?#]*\/)*)/
|
31
|
+
IPADDR = /\A#{PATTERN::IPADDR}\Z/
|
32
|
+
# HDN = /\A#{PATTERN::HOSTNAME}\Z/
|
33
|
+
# TOKEN = /\A#{PATTERN::TOKEN}\Z/
|
34
|
+
# TWO_DOT_DOMAINS = /\A\.(com|edu|net|mil|gov|int|org)\Z/
|
35
|
+
|
15
36
|
# The mandatory name and value of the cookie
|
16
37
|
attr_reader :name, :value
|
17
38
|
# The domain and path of the cookie. These values will be set on all
|
@@ -54,25 +75,24 @@ module CookieJar
|
|
54
75
|
end
|
55
76
|
|
56
77
|
def expired?
|
57
|
-
expires_at != nil &&
|
78
|
+
expires_at != nil && Time.now > expires_at
|
58
79
|
end
|
59
80
|
|
60
81
|
# Create a cookie based on an absolute URI and the string value of a
|
61
82
|
# 'Set-Cookie' header.
|
62
|
-
def self.from_set_cookie
|
63
|
-
|
64
|
-
|
65
|
-
params=set_cookie_value.split(/;\s*/)
|
83
|
+
def self.from_set_cookie request_uri, set_cookie_value
|
84
|
+
args = { }
|
85
|
+
params=set_cookie_value.split /;\s*/
|
66
86
|
params.each do |param|
|
67
87
|
result = PARAM1.match param
|
68
|
-
if
|
69
|
-
raise InvalidCookieError.new
|
88
|
+
if !result
|
89
|
+
raise InvalidCookieError.new "Invalid cookie parameter in cookie '#{set_cookie_value}'"
|
70
90
|
end
|
71
91
|
key = result[1].downcase.to_sym
|
72
92
|
keyvalue = result[2]
|
73
93
|
case key
|
74
94
|
when :expires
|
75
|
-
args[:expires_at] = Time.parse
|
95
|
+
args[:expires_at] = Time.parse keyvalue
|
76
96
|
when :domain
|
77
97
|
args[:domain] = keyvalue
|
78
98
|
when :path
|
@@ -95,7 +115,7 @@ module CookieJar
|
|
95
115
|
end
|
96
116
|
|
97
117
|
def to_s
|
98
|
-
%^"#{name}=#{value}#{if
|
118
|
+
%^"#{name}=#{value}#{if domain then "; domain=#{domain}" end}#{if expiry then "; expiry=#{expiry}" end}#{if path then "; path=#{path}" end}#{if secure then "; secure" end }#{if http_only then "; HTTPOnly" end}^
|
99
119
|
end
|
100
120
|
|
101
121
|
# Check whether a cookie meets all of the rules to be created, based on
|
@@ -153,7 +173,7 @@ module CookieJar
|
|
153
173
|
end
|
154
174
|
end
|
155
175
|
|
156
|
-
raise InvalidCookieError.new
|
176
|
+
raise InvalidCookieError.new errors unless errors.empty?
|
157
177
|
|
158
178
|
# Note: 'secure' is not explicitly defined as an SSL channel, and no
|
159
179
|
# test is defined around validity and the 'secure' attribute
|
@@ -173,11 +193,112 @@ module CookieJar
|
|
173
193
|
expiry_match = !expired?
|
174
194
|
path_match && secure_match && script_match && expiry_match
|
175
195
|
end
|
196
|
+
|
197
|
+
# Compute the base of a path.
|
198
|
+
def self.cookie_base_path path
|
199
|
+
path = path.is_a?(URI) ? path.path : path
|
200
|
+
BASE_PATH.match(path)[1]
|
201
|
+
end
|
202
|
+
|
203
|
+
# Processes cookie path data using the following rules:
|
204
|
+
# Paths are separated by '/' characters, and accepted values are truncated
|
205
|
+
# to the last '/' character. If no path is specified in the cookie, a path
|
206
|
+
# value will be taken from the request URI which was used for the site.
|
207
|
+
#
|
208
|
+
# Note that this will not attempt to detect a mismatch of the request uri domain
|
209
|
+
# and explicitly specified cookie path
|
210
|
+
def self.determine_cookie_path request_uri, cookie_path
|
211
|
+
uri = request_uri.is_a?(URI) ? request_uri : URI.parse(request_uri)
|
212
|
+
cookie_path = cookie_path.is_a?(Cookie) ? cookie_path.path : cookie_path
|
213
|
+
|
214
|
+
if cookie_path == nil || cookie_path.empty?
|
215
|
+
cookie_path = cookie_base_path uri.path
|
216
|
+
end
|
217
|
+
cookie_path
|
218
|
+
end
|
219
|
+
|
220
|
+
# Given a URI, compute the relevant search domains for pre-existing
|
221
|
+
# cookies. This includes all the valid dotted forms for a named or IP
|
222
|
+
# domains.
|
223
|
+
def self.compute_search_domains request_uri
|
224
|
+
uri = request_uri.is_a?(URI) ? request_uri : URI.parse(request_uri)
|
225
|
+
host = effective_host uri
|
226
|
+
result = [host]
|
227
|
+
if host !~ IPADDR
|
228
|
+
result << ".#{host}"
|
229
|
+
end
|
230
|
+
base = hostname_reach host
|
231
|
+
if base
|
232
|
+
result << ".#{base}"
|
233
|
+
end
|
234
|
+
result
|
235
|
+
end
|
236
|
+
|
237
|
+
# Processes cookie domain data using the following rules:
|
238
|
+
# Domains strings of the form .foo.com match 'foo.com' and all immediate
|
239
|
+
# subdomains of 'foo.com'. Domain strings specified of the form 'foo.com' are
|
240
|
+
# modified to '.foo.com', and as such will still apply to subdomains.
|
241
|
+
#
|
242
|
+
# Cookies without an explicit domain will have their domain value taken directly
|
243
|
+
# from the URL, and will _NOT_ have any leading dot applied. For example, a request
|
244
|
+
# to http://foo.com/ will cause an entry for 'foo.com' to be created - which applies
|
245
|
+
# to foo.com but no subdomain.
|
246
|
+
#
|
247
|
+
# Note that this will not attempt to detect a mismatch of the request uri domain
|
248
|
+
# and explicitly specified cookie domain
|
249
|
+
def self.determine_cookie_domain request_uri, cookie_domain
|
250
|
+
uri = request_uri.is_a?(URI) ? request_uri : URI.parse(request_uri)
|
251
|
+
domain = cookie_domain.is_a?(Cookie) ? cookie_domain.domain : cookie_domain
|
252
|
+
|
253
|
+
if domain == nil || domain.empty?
|
254
|
+
domain = effective_host uri.host
|
255
|
+
else
|
256
|
+
domain = domain.downcase
|
257
|
+
if domain =~ IPADDR || domain.start_with?('.')
|
258
|
+
domain
|
259
|
+
else
|
260
|
+
".#{domain}"
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
# Compute the effective host (RFC 2965, section 1)
|
265
|
+
# [host] a string or URI.
|
266
|
+
#
|
267
|
+
# Has the added additional logic of searching for interior dots specifically, and
|
268
|
+
# matches colons to prevent .local being suffixed on IPv6 addresses
|
269
|
+
def self.effective_host host
|
270
|
+
hostname = host.is_a?(URI) ? host.host : host
|
271
|
+
hostname = hostname.downcase
|
272
|
+
|
273
|
+
if /.[\.:]./.match(hostname) || hostname == '.local'
|
274
|
+
hostname
|
275
|
+
else
|
276
|
+
hostname + '.local'
|
277
|
+
end
|
278
|
+
end
|
279
|
+
# Compare a base domain against the base domain to see if they match, or
|
280
|
+
# if the base domain is reachable
|
281
|
+
def self.domains_match tested_domain,base_domain
|
282
|
+
return true if (tested_domain == base_domain || ".#{tested_domain}" == base_domain)
|
283
|
+
lhs = effective_host tested_domain
|
284
|
+
rhs = effective_host base_domain
|
285
|
+
lhs == rhs || ".#{lhs}" == rhs || hostname_reach(lhs) == rhs || ".#{hostname_reach lhs}" == rhs
|
286
|
+
end
|
287
|
+
# Compute the reach of a hostname (RFC 2965, section 1)
|
288
|
+
# Determines the next highest superdomain, or nil if none valid
|
289
|
+
def self.hostname_reach hostname
|
290
|
+
host = hostname.is_a?(URI) ? hostname.host : hostname
|
291
|
+
host = host.downcase
|
292
|
+
match = BASE_HOSTNAME.match host
|
293
|
+
if match
|
294
|
+
match[1]
|
295
|
+
end
|
296
|
+
end
|
176
297
|
protected
|
177
298
|
PARAM1 = /\A(#{PATTERN::TOKEN})(?:=#{PATTERN::VALUE1})?\Z/
|
178
299
|
# PARAM2 = /\A(#{PATTERN::TOKEN})(?:=#{PATTERN::VALUE2})?\Z/
|
179
300
|
|
180
|
-
def initialize
|
301
|
+
def initialize *params
|
181
302
|
case params.length
|
182
303
|
when 1
|
183
304
|
args = params[0]
|
data/lib/cookiejar/jar.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'cookiejar/
|
1
|
+
require 'cookiejar/cookie'
|
2
2
|
|
3
3
|
# A cookie store for client side usage.
|
4
4
|
# - Enforces cookie validity rules
|
@@ -42,7 +42,6 @@ require 'cookiejar/cookie_logic'
|
|
42
42
|
# path.
|
43
43
|
module CookieJar
|
44
44
|
class Jar
|
45
|
-
include CookieLogic
|
46
45
|
def initialize
|
47
46
|
@domains = {}
|
48
47
|
end
|
@@ -53,10 +52,8 @@ module CookieJar
|
|
53
52
|
# returns the Cookie object on success, otherwise raises an
|
54
53
|
# InvalidCookieError
|
55
54
|
def set_cookie request_uri, cookie_header_value
|
56
|
-
|
57
|
-
|
58
|
-
cookie = Cookie.from_set_cookie(uri, cookie_header_value)
|
59
|
-
domain_paths = find_or_add_domain_for_cookie(cookie.domain)
|
55
|
+
cookie = Cookie.from_set_cookie request_uri, cookie_header_value
|
56
|
+
domain_paths = find_or_add_domain_for_cookie cookie
|
60
57
|
add_cookie_to_path(domain_paths,cookie)
|
61
58
|
cookie
|
62
59
|
end
|
@@ -69,11 +66,11 @@ module CookieJar
|
|
69
66
|
# - :script - if set, cookies set to be HTTP-only will be ignored
|
70
67
|
def get_cookies request_uri, args = {}
|
71
68
|
uri = request_uri.is_a?(URI)? request_uri : URI.parse(request_uri)
|
72
|
-
hosts = compute_search_domains uri
|
69
|
+
hosts = Cookie.compute_search_domains uri
|
73
70
|
|
74
71
|
results = []
|
75
72
|
hosts.each do |host|
|
76
|
-
domain =
|
73
|
+
domain = find_domain host
|
77
74
|
domain.each do |path, cookies|
|
78
75
|
if uri.path.start_with? path
|
79
76
|
results += cookies.select do |name, cookie|
|
@@ -105,14 +102,12 @@ module CookieJar
|
|
105
102
|
|
106
103
|
protected
|
107
104
|
|
108
|
-
def
|
109
|
-
|
110
|
-
@domains[domain] || {}
|
105
|
+
def find_domain host
|
106
|
+
@domains[host] || {}
|
111
107
|
end
|
112
108
|
|
113
|
-
def find_or_add_domain_for_cookie
|
114
|
-
domain
|
115
|
-
@domains[domain] ||= {}
|
109
|
+
def find_or_add_domain_for_cookie cookie
|
110
|
+
@domains[cookie.domain] ||= {}
|
116
111
|
end
|
117
112
|
|
118
113
|
def add_cookie_to_path (paths, cookie)
|
data/lib/cookiejar.rb
CHANGED
data/test/cookie_test.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
require 'cookiejar'
|
2
|
-
require 'cookiejar/cookie_logic'
|
3
2
|
|
4
3
|
include CookieJar
|
5
|
-
include CookieLogic
|
6
4
|
|
7
5
|
FOO_URL = 'http://localhost/foo'
|
8
6
|
AMMO_URL = 'http://localhost/ammo'
|
@@ -31,6 +29,14 @@ describe Cookie do
|
|
31
29
|
cookie.name.should == 'foo'
|
32
30
|
cookie.value.should == 'bar'
|
33
31
|
end
|
32
|
+
it "should normalize domain names" do
|
33
|
+
cookie = Cookie.from_set_cookie 'http://localhost/', 'foo=Bar;domain=LoCaLHoSt.local'
|
34
|
+
cookie.domain.should == '.localhost.local'
|
35
|
+
end
|
36
|
+
it "should accept non-normalized .local" do
|
37
|
+
cookie = Cookie.from_set_cookie 'http://localhost/', 'foo=bar;domain=.local'
|
38
|
+
cookie.domain.should == '.local'
|
39
|
+
end
|
34
40
|
end
|
35
41
|
describe '.validate_cookie' do
|
36
42
|
localaddr = 'http://localhost/foo/bar/'
|
@@ -112,4 +118,153 @@ describe Cookie do
|
|
112
118
|
Cookie.validate_cookie('http://foo.com/bar/baz/', higher)
|
113
119
|
end
|
114
120
|
end
|
121
|
+
describe '.cookie_base_path' do
|
122
|
+
it "should leave '/' alone" do
|
123
|
+
Cookie.cookie_base_path('/').should == '/'
|
124
|
+
end
|
125
|
+
it "should strip off everything after the last '/'" do
|
126
|
+
Cookie.cookie_base_path('/foo/bar/baz').should == '/foo/bar/'
|
127
|
+
end
|
128
|
+
it "should handle query parameters and fragments with slashes" do
|
129
|
+
Cookie.cookie_base_path('/foo/bar?query=a/b/c#fragment/b/c').should == '/foo/'
|
130
|
+
end
|
131
|
+
it "should handle URI objects" do
|
132
|
+
Cookie.cookie_base_path(URI.parse('http://www.foo.com/bar/')).should == '/bar/'
|
133
|
+
end
|
134
|
+
it "should preserve case" do
|
135
|
+
Cookie.cookie_base_path("/BaR/").should == '/BaR/'
|
136
|
+
end
|
137
|
+
end
|
138
|
+
describe '.determine_cookie_path' do
|
139
|
+
it "should use the requested path when none is specified for the cookie" do
|
140
|
+
Cookie.determine_cookie_path('http://foo.com/', nil).should == '/'
|
141
|
+
Cookie.determine_cookie_path('http://foo.com/bar/baz', '').should == '/bar/'
|
142
|
+
end
|
143
|
+
it "should handle URI objects" do
|
144
|
+
Cookie.determine_cookie_path(URI.parse('http://foo.com/bar/'), '').should == '/bar/'
|
145
|
+
end
|
146
|
+
it "should handle Cookie objects" do
|
147
|
+
cookie = Cookie.from_set_cookie('http://foo.com/', "name=value;path=/")
|
148
|
+
Cookie.determine_cookie_path('http://foo.com/', cookie).should == '/'
|
149
|
+
end
|
150
|
+
it "should ignore the request when a path is specified" do
|
151
|
+
Cookie.determine_cookie_path('http://foo.com/ignorable/path', '/path/').should == '/path/'
|
152
|
+
end
|
153
|
+
end
|
154
|
+
describe '.compute_search_domains' do
|
155
|
+
it "should handle subdomains" do
|
156
|
+
Cookie.compute_search_domains('http://www.auth.foo.com/').should ==
|
157
|
+
['www.auth.foo.com', '.www.auth.foo.com', '.auth.foo.com']
|
158
|
+
end
|
159
|
+
it "should handle root domains" do
|
160
|
+
Cookie.compute_search_domains('http://foo.com/').should ==
|
161
|
+
['foo.com', '.foo.com']
|
162
|
+
end
|
163
|
+
it "should handle IP addresses" do
|
164
|
+
Cookie.compute_search_domains('http://127.0.0.1/').should ==
|
165
|
+
['127.0.0.1']
|
166
|
+
end
|
167
|
+
it "should handle local addresses" do
|
168
|
+
Cookie.compute_search_domains('http://zero/').should ==
|
169
|
+
['zero.local', '.zero.local', '.local']
|
170
|
+
end
|
171
|
+
end
|
172
|
+
describe '.determine_cookie_domain' do
|
173
|
+
it "should add a dot to the front of domains" do
|
174
|
+
Cookie.determine_cookie_domain('http://foo.com/', 'foo.com').should == '.foo.com'
|
175
|
+
end
|
176
|
+
it "should not add a second dot if one present" do
|
177
|
+
Cookie.determine_cookie_domain('http://foo.com/', '.foo.com').should == '.foo.com'
|
178
|
+
end
|
179
|
+
it "should handle Cookie objects" do
|
180
|
+
c = Cookie.from_set_cookie('http://foo.com/', "foo=bar;domain=foo.com")
|
181
|
+
Cookie.determine_cookie_domain('http://foo.com/', c).should == '.foo.com'
|
182
|
+
end
|
183
|
+
it "should handle URI objects" do
|
184
|
+
Cookie.determine_cookie_domain(URI.parse('http://foo.com/'), '.foo.com').should == '.foo.com'
|
185
|
+
end
|
186
|
+
it "should use an exact hostname when no domain specified" do
|
187
|
+
Cookie.determine_cookie_domain('http://foo.com/', '').should == 'foo.com'
|
188
|
+
end
|
189
|
+
it "should leave IPv4 addresses alone" do
|
190
|
+
Cookie.determine_cookie_domain('http://127.0.0.1/', '127.0.0.1').should == '127.0.0.1'
|
191
|
+
end
|
192
|
+
it "should leave IPv6 addresses alone" do
|
193
|
+
['2001:db8:85a3::8a2e:370:7334', '::ffff:192.0.2.128'].each do |value|
|
194
|
+
Cookie.determine_cookie_domain("http://[#{value}]/", value).should == value
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
describe ".effective_host" do
|
199
|
+
it "should leave proper domains the same" do
|
200
|
+
['google.com', 'www.google.com', 'google.com.'].each do |value|
|
201
|
+
Cookie.effective_host(value).should == value
|
202
|
+
end
|
203
|
+
end
|
204
|
+
it "should handle a URI object" do
|
205
|
+
Cookie.effective_host(URI.parse('http://example.com/')).should == 'example.com'
|
206
|
+
end
|
207
|
+
it "should add a local suffix on unqualified hosts" do
|
208
|
+
Cookie.effective_host('localhost').should == 'localhost.local'
|
209
|
+
end
|
210
|
+
it "should leave IPv4 addresses alone" do
|
211
|
+
Cookie.effective_host('127.0.0.1').should == '127.0.0.1'
|
212
|
+
end
|
213
|
+
it "should leave IPv6 addresses alone" do
|
214
|
+
['2001:db8:85a3::8a2e:370:7334', ':ffff:192.0.2.128'].each do |value|
|
215
|
+
Cookie.effective_host(value).should == value
|
216
|
+
end
|
217
|
+
end
|
218
|
+
it "should lowercase addresses" do
|
219
|
+
Cookie.effective_host('FOO.COM').should == 'foo.com'
|
220
|
+
end
|
221
|
+
end
|
222
|
+
describe '.domains_match' do
|
223
|
+
it "should handle exact matches" do
|
224
|
+
Cookie.domains_match('foo', 'foo').should be_true
|
225
|
+
Cookie.domains_match('foo.com', 'foo.com').should be_true
|
226
|
+
Cookie.domains_match('127.0.0.1', '127.0.0.1').should be_true
|
227
|
+
Cookie.domains_match('::ffff:192.0.2.128', '::ffff:192.0.2.128').should be_true
|
228
|
+
end
|
229
|
+
it "should handle matching a superdomain" do
|
230
|
+
Cookie.domains_match('auth.foo.com', 'foo.com').should be_true
|
231
|
+
Cookie.domains_match('x.y.z.foo.com', 'y.z.foo.com').should be_true
|
232
|
+
end
|
233
|
+
it "should not match superdomains, or illegal domains" do
|
234
|
+
Cookie.domains_match('x.y.z.foo.com', 'z.foo.com').should be_false
|
235
|
+
Cookie.domains_match('foo.com', 'com').should be_false
|
236
|
+
end
|
237
|
+
it "should not match domains with and without a dot suffix together" do
|
238
|
+
Cookie.domains_match('foo.com.', 'foo.com').should be_false
|
239
|
+
end
|
240
|
+
end
|
241
|
+
describe '.hostname_reach' do
|
242
|
+
it "should find the next highest subdomain" do
|
243
|
+
{'www.google.com' => 'google.com', 'auth.corp.companyx.com' => 'corp.companyx.com'}.each do |entry|
|
244
|
+
Cookie.hostname_reach(entry[0]).should == entry[1]
|
245
|
+
end
|
246
|
+
end
|
247
|
+
it "should handle domains with suffixed dots" do
|
248
|
+
Cookie.hostname_reach('www.google.com.').should == 'google.com.'
|
249
|
+
end
|
250
|
+
it "should return nil for a root domain" do
|
251
|
+
Cookie.hostname_reach('github.com').should be_nil
|
252
|
+
end
|
253
|
+
it "should return 'local' for a local domain" do
|
254
|
+
['foo.local', 'foo.local.'].each do |hostname|
|
255
|
+
Cookie.hostname_reach(hostname).should == 'local'
|
256
|
+
end
|
257
|
+
end
|
258
|
+
it "should handle mixed-case '.local'" do
|
259
|
+
Cookie.hostname_reach('foo.LOCAL').should == 'local'
|
260
|
+
end
|
261
|
+
it "should return nil for an IPv4 address" do
|
262
|
+
Cookie.hostname_reach('127.0.0.1').should be_nil
|
263
|
+
end
|
264
|
+
it "should return nil for IPv6 addresses" do
|
265
|
+
['2001:db8:85a3::8a2e:370:7334', '::ffff:192.0.2.128'].each do |value|
|
266
|
+
Cookie.hostname_reach(value).should be_nil
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
115
270
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dwaite-cookiejar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Waite
|
@@ -24,14 +24,13 @@ extra_rdoc_files: []
|
|
24
24
|
files:
|
25
25
|
- lib/cookiejar/cookie.rb
|
26
26
|
- lib/cookiejar/cookie_common.rb
|
27
|
-
- lib/cookiejar/cookie_logic.rb
|
28
27
|
- lib/cookiejar/jar.rb
|
29
28
|
- lib/cookiejar.rb
|
30
|
-
- test/cookie_logic_test.rb
|
31
29
|
- test/cookie_test.rb
|
32
30
|
- test/jar_test.rb
|
33
31
|
has_rdoc: false
|
34
32
|
homepage: http://alkaline-solutions.com
|
33
|
+
licenses:
|
35
34
|
post_install_message:
|
36
35
|
rdoc_options:
|
37
36
|
- --title
|
@@ -53,7 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
52
|
requirements: []
|
54
53
|
|
55
54
|
rubyforge_project:
|
56
|
-
rubygems_version: 1.
|
55
|
+
rubygems_version: 1.3.5
|
57
56
|
signing_key:
|
58
57
|
specification_version: 3
|
59
58
|
summary: Client-side HTTP Cookie library
|
@@ -1,129 +0,0 @@
|
|
1
|
-
require 'uri'
|
2
|
-
require 'cookiejar/cookie_common'
|
3
|
-
|
4
|
-
module CookieJar
|
5
|
-
module CookieLogic
|
6
|
-
module PATTERN
|
7
|
-
include URI::REGEXP::PATTERN
|
8
|
-
|
9
|
-
TOKEN = '[^(),\/<>@;:\\\"\[\]?={}\s]*'
|
10
|
-
VALUE1 = "([^;]*)"
|
11
|
-
IPADDR = "#{IPV4ADDR}|#{IPV6ADDR}"
|
12
|
-
BASE_HOSTNAME = "(?:#{DOMLABEL}\\.)((?:(?:#{DOMLABEL}\\.)+(?:#{TOPLABEL}\\.?)|local))"
|
13
|
-
|
14
|
-
# QUOTED_PAIR = "\\\\[\\x00-\\x7F]"
|
15
|
-
# LWS = "\\r\\n(?:[ \\t]+)"
|
16
|
-
# TEXT="[\\t\\x20-\\x7E\\x80-\\xFF]|(?:#{LWS})"
|
17
|
-
# QDTEXT="[\\t\\x20-\\x21\\x23-\\x7E\\x80-\\xFF]|(?:#{LWS})"
|
18
|
-
# QUOTED_TEXT = "\\\"((?:#{QDTEXT}|#{QUOTED_PAIR})*)\\\""
|
19
|
-
# VALUE2 = "(#{TOKEN})|#{QUOTED_TEXT}"
|
20
|
-
|
21
|
-
end
|
22
|
-
BASE_HOSTNAME = /#{PATTERN::BASE_HOSTNAME}/
|
23
|
-
BASE_PATH = /\A((?:[^\/?#]*\/)*)/
|
24
|
-
IPADDR = /\A#{PATTERN::IPADDR}\Z/
|
25
|
-
# HDN = /\A#{PATTERN::HOSTNAME}\Z/
|
26
|
-
# TOKEN = /\A#{PATTERN::TOKEN}\Z/
|
27
|
-
# TWO_DOT_DOMAINS = /\A\.(com|edu|net|mil|gov|int|org)\Z/
|
28
|
-
|
29
|
-
# Compute the effective host (RFC 2965, section 1)
|
30
|
-
# [host] a string or URI.
|
31
|
-
#
|
32
|
-
# Has the added additional logic of searching for interior dots specifically, and
|
33
|
-
# matches colons to prevent .local being suffixed on IPv6 addresses
|
34
|
-
def effective_host(host)
|
35
|
-
hostname = host.is_a?(URI) ? host.host : host
|
36
|
-
hostname = hostname.downcase
|
37
|
-
|
38
|
-
if /.(?:[\.:])./.match(hostname)
|
39
|
-
hostname
|
40
|
-
else
|
41
|
-
hostname + '.local'
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Processes cookie domain data using the following rules:
|
46
|
-
# Domains strings of the form .foo.com match 'foo.com' and all immediate
|
47
|
-
# subdomains of 'foo.com'. Domain strings specified of the form 'foo.com' are
|
48
|
-
# modified to '.foo.com', and as such will still apply to subdomains.
|
49
|
-
#
|
50
|
-
# Cookies without an explicit domain will have their domain value taken directly
|
51
|
-
# from the URL, and will _NOT_ have any leading dot applied. For example, a request
|
52
|
-
# to http://foo.com/ will cause an entry for 'foo.com' to be created - which applies
|
53
|
-
# to foo.com but no subdomain.
|
54
|
-
#
|
55
|
-
# Note that this will not attempt to detect a mismatch of the request uri domain
|
56
|
-
# and explicitly specified cookie domain
|
57
|
-
def determine_cookie_domain (request_uri, cookie_domain)
|
58
|
-
uri = request_uri.is_a?(URI) ? request_uri : URI.parse(request_uri)
|
59
|
-
domain = cookie_domain.is_a?(Cookie) ? cookie_domain.domain : cookie_domain
|
60
|
-
|
61
|
-
if domain == nil || domain.empty?
|
62
|
-
domain = effective_host(uri.host)
|
63
|
-
else
|
64
|
-
domain = domain.downcase
|
65
|
-
if (domain =~ IPADDR || domain.start_with?('.'))
|
66
|
-
domain
|
67
|
-
else
|
68
|
-
".#{domain}"
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
# Processes cookie path data using the following rules:
|
74
|
-
# Paths are separated by '/' characters, and accepted values are truncated
|
75
|
-
# to the last '/' character. If no path is specified in the cookie, a path
|
76
|
-
# value will be taken from the request URI which was used for the site.
|
77
|
-
#
|
78
|
-
# Note that this will not attempt to detect a mismatch of the request uri domain
|
79
|
-
# and explicitly specified cookie path
|
80
|
-
def determine_cookie_path(request_uri, cookie_path)
|
81
|
-
uri = request_uri.is_a?(URI) ? request_uri : URI.parse(request_uri)
|
82
|
-
cookie_path = cookie_path.is_a?(Cookie) ? cookie_path.path : cookie_path
|
83
|
-
|
84
|
-
if (cookie_path == nil || cookie_path.empty?)
|
85
|
-
cookie_path = cookie_base_path uri.path
|
86
|
-
end
|
87
|
-
cookie_path
|
88
|
-
end
|
89
|
-
|
90
|
-
# Compute the reach of a hostname (RFC 2965, section 1)
|
91
|
-
# Determines the next highest superdomain, or nil if none valid
|
92
|
-
def hostname_reach hostname
|
93
|
-
host = hostname.is_a?(URI) ? hostname.host : hostname
|
94
|
-
BASE_HOSTNAME.match(host) && $~[1] || nil
|
95
|
-
end
|
96
|
-
|
97
|
-
# Compute the base of a path.
|
98
|
-
def cookie_base_path(path)
|
99
|
-
path = path.is_a?(URI) ? path.path : path
|
100
|
-
BASE_PATH.match(path)[1]
|
101
|
-
end
|
102
|
-
|
103
|
-
# Compare a base domain against the base domain to see if they match, or
|
104
|
-
# if the base domain is reachable
|
105
|
-
def domains_match tested_domain,base_domain
|
106
|
-
return true if (tested_domain == base_domain || ".#{tested_domain}" == base_domain)
|
107
|
-
lhs = effective_host tested_domain
|
108
|
-
rhs = effective_host base_domain
|
109
|
-
lhs == rhs || ".#{lhs}" == rhs || hostname_reach(lhs) == rhs || ".#{hostname_reach lhs}" == rhs
|
110
|
-
end
|
111
|
-
|
112
|
-
# Given a URI, compute the relevant search domains for pre-existing
|
113
|
-
# cookies. This includes all the valid dotted forms for a named or IP
|
114
|
-
# domains.
|
115
|
-
def compute_search_domains request_uri
|
116
|
-
uri = request_uri.is_a?(URI) ? request_uri : URI.parse(request_uri)
|
117
|
-
host = effective_host uri
|
118
|
-
result = [host]
|
119
|
-
if (host !~ IPADDR)
|
120
|
-
result << ".#{host}"
|
121
|
-
end
|
122
|
-
base = hostname_reach host
|
123
|
-
if (base)
|
124
|
-
result << ".#{base}"
|
125
|
-
end
|
126
|
-
result
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
data/test/cookie_logic_test.rb
DELETED
@@ -1,157 +0,0 @@
|
|
1
|
-
require 'cookiejar'
|
2
|
-
require 'cookiejar/cookie_logic'
|
3
|
-
include CookieJar
|
4
|
-
|
5
|
-
describe CookieLogic do
|
6
|
-
include CookieLogic
|
7
|
-
|
8
|
-
describe ".effective_host" do
|
9
|
-
it "should leave proper domains the same" do
|
10
|
-
['google.com', 'www.google.com', 'google.com.'].each do |value|
|
11
|
-
effective_host(value).should == value
|
12
|
-
end
|
13
|
-
end
|
14
|
-
it "should handle a URI object" do
|
15
|
-
effective_host(URI.parse('http://example.com/')).should == 'example.com'
|
16
|
-
end
|
17
|
-
it "should add a local suffix on unqualified hosts" do
|
18
|
-
effective_host('localhost').should == 'localhost.local'
|
19
|
-
end
|
20
|
-
it "should leave IPv4 addresses alone" do
|
21
|
-
effective_host('127.0.0.1').should == '127.0.0.1'
|
22
|
-
end
|
23
|
-
it "should leave IPv6 addresses alone" do
|
24
|
-
['2001:db8:85a3::8a2e:370:7334', ':ffff:192.0.2.128'].each do |value|
|
25
|
-
effective_host(value).should == value
|
26
|
-
end
|
27
|
-
end
|
28
|
-
it "should lowercase addresses" do
|
29
|
-
effective_host('FOO.COM').should == 'foo.com'
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
describe '.hostname_reach' do
|
34
|
-
it "should find the next highest subdomain" do
|
35
|
-
{'www.google.com' => 'google.com', 'auth.corp.companyx.com' => 'corp.companyx.com'}.each do |entry|
|
36
|
-
hostname_reach(entry[0]).should == entry[1]
|
37
|
-
end
|
38
|
-
end
|
39
|
-
it "should handle domains with suffixed dots" do
|
40
|
-
hostname_reach('www.google.com.').should == 'google.com.'
|
41
|
-
end
|
42
|
-
it "should return nil for a root domain" do
|
43
|
-
hostname_reach('github.com').should be_nil
|
44
|
-
end
|
45
|
-
it "should return 'local' for a local domain" do
|
46
|
-
['foo.local', 'foo.local.'].each do |hostname|
|
47
|
-
hostname_reach(hostname).should == 'local'
|
48
|
-
end
|
49
|
-
end
|
50
|
-
it "should return nil for an IPv4 address" do
|
51
|
-
hostname_reach('127.0.0.1').should be_nil
|
52
|
-
end
|
53
|
-
it "should return nil for IPv6 addresses" do
|
54
|
-
['2001:db8:85a3::8a2e:370:7334', '::ffff:192.0.2.128'].each do |value|
|
55
|
-
hostname_reach(value).should be_nil
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
describe '.cookie_base_path' do
|
61
|
-
it "should leave '/' alone" do
|
62
|
-
cookie_base_path('/').should == '/'
|
63
|
-
end
|
64
|
-
it "should strip off everything after the last '/'" do
|
65
|
-
cookie_base_path('/foo/bar/baz').should == '/foo/bar/'
|
66
|
-
end
|
67
|
-
it "should handle query parameters and fragments with slashes" do
|
68
|
-
cookie_base_path('/foo/bar?query=a/b/c#fragment/b/c').should == '/foo/'
|
69
|
-
end
|
70
|
-
it "should handle URI objects" do
|
71
|
-
cookie_base_path(URI.parse('http://www.foo.com/bar/')).should == '/bar/'
|
72
|
-
end
|
73
|
-
it "should preserve case" do
|
74
|
-
cookie_base_path("/BaR/").should == '/BaR/'
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
describe '.determine_cookie_domain' do
|
79
|
-
it "should add a dot to the front of domains" do
|
80
|
-
determine_cookie_domain('http://foo.com/', 'foo.com').should == '.foo.com'
|
81
|
-
end
|
82
|
-
it "should not add a second dot if one present" do
|
83
|
-
determine_cookie_domain('http://foo.com/', '.foo.com').should == '.foo.com'
|
84
|
-
end
|
85
|
-
it "should handle Cookie objects" do
|
86
|
-
c = Cookie.from_set_cookie('http://foo.com/', "foo=bar;domain=foo.com")
|
87
|
-
determine_cookie_domain('http://foo.com/', c).should == '.foo.com'
|
88
|
-
end
|
89
|
-
it "should handle URI objects" do
|
90
|
-
determine_cookie_domain(URI.parse('http://foo.com/'), '.foo.com').should == '.foo.com'
|
91
|
-
end
|
92
|
-
it "should use an exact hostname when no domain specified" do
|
93
|
-
determine_cookie_domain('http://foo.com/', '').should == 'foo.com'
|
94
|
-
end
|
95
|
-
it "should leave IPv4 addresses alone" do
|
96
|
-
determine_cookie_domain('http://127.0.0.1/', '127.0.0.1').should == '127.0.0.1'
|
97
|
-
end
|
98
|
-
it "should leave IPv6 addresses alone" do
|
99
|
-
['2001:db8:85a3::8a2e:370:7334', '::ffff:192.0.2.128'].each do |value|
|
100
|
-
determine_cookie_domain("http://[#{value}]/", value).should == value
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
describe '.determine_cookie_path' do
|
105
|
-
it "should use the requested path when none is specified for the cookie" do
|
106
|
-
determine_cookie_path('http://foo.com/', nil).should == '/'
|
107
|
-
determine_cookie_path('http://foo.com/bar/baz', '').should == '/bar/'
|
108
|
-
end
|
109
|
-
it "should handle URI objects" do
|
110
|
-
determine_cookie_path(URI.parse('http://foo.com/bar/'), '').should == '/bar/'
|
111
|
-
end
|
112
|
-
it "should handle Cookie objects" do
|
113
|
-
cookie = Cookie.from_set_cookie('http://foo.com/', "name=value;path=/")
|
114
|
-
determine_cookie_path('http://foo.com/', cookie).should == '/'
|
115
|
-
end
|
116
|
-
it "should ignore the request when a path is specified" do
|
117
|
-
determine_cookie_path('http://foo.com/ignorable/path', '/path/').should == '/path/'
|
118
|
-
end
|
119
|
-
end
|
120
|
-
describe '.domains_match' do
|
121
|
-
it "should handle exact matches" do
|
122
|
-
domains_match('foo', 'foo').should be_true
|
123
|
-
domains_match('foo.com', 'foo.com').should be_true
|
124
|
-
domains_match('127.0.0.1', '127.0.0.1').should be_true
|
125
|
-
domains_match('::ffff:192.0.2.128', '::ffff:192.0.2.128').should be_true
|
126
|
-
end
|
127
|
-
it "should handle matching a superdomain" do
|
128
|
-
domains_match('auth.foo.com', 'foo.com').should be_true
|
129
|
-
domains_match('x.y.z.foo.com', 'y.z.foo.com').should be_true
|
130
|
-
end
|
131
|
-
it "should not match superdomains, or illegal domains" do
|
132
|
-
domains_match('x.y.z.foo.com', 'z.foo.com').should be_false
|
133
|
-
domains_match('foo.com', 'com').should be_false
|
134
|
-
end
|
135
|
-
it "should not match domains with and without a dot suffix together" do
|
136
|
-
domains_match('foo.com.', 'foo.com').should be_false
|
137
|
-
end
|
138
|
-
end
|
139
|
-
describe '.compute_search_domains' do
|
140
|
-
it "should handle subdomains" do
|
141
|
-
compute_search_domains('http://www.auth.foo.com/').should ==
|
142
|
-
['www.auth.foo.com', '.www.auth.foo.com', '.auth.foo.com']
|
143
|
-
end
|
144
|
-
it "should handle root domains" do
|
145
|
-
compute_search_domains('http://foo.com/').should ==
|
146
|
-
['foo.com', '.foo.com']
|
147
|
-
end
|
148
|
-
it "should handle IP addresses" do
|
149
|
-
compute_search_domains('http://127.0.0.1/').should ==
|
150
|
-
['127.0.0.1']
|
151
|
-
end
|
152
|
-
it "should handle local addresses" do
|
153
|
-
compute_search_domains('http://zero/').should ==
|
154
|
-
['zero.local', '.zero.local', '.local']
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|