dwaite-cookiejar 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,6 @@
1
1
  require 'time'
2
- require 'cookiejar/cookie_logic'
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
- include CookieLogic
13
- extend CookieLogic
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 && (Time.now > expires_at)
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(request_uri, set_cookie_value)
63
- include CookieLogic
64
- args = {}
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 (!result)
69
- raise InvalidCookieError.new("Invalid cookie parameter in cookie '#{set_cookie_value}'")
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(keyvalue)
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(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}^
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(errors) unless errors.empty?
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(*params)
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/cookie_logic'
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
- uri = request_uri.is_a?(URI) ? request_uri : URI.parse(request_uri)
57
- host = effective_host uri
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 = find_domain_for_cookie host
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 find_domain_for_cookie domain
109
- domain = effective_host domain
110
- @domains[domain] || {}
105
+ def find_domain host
106
+ @domains[host] || {}
111
107
  end
112
108
 
113
- def find_or_add_domain_for_cookie(domain)
114
- domain = effective_host 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
@@ -1,2 +1,3 @@
1
+ require 'cookiejar/cookie_common'
1
2
  require 'cookiejar/cookie'
2
3
  require 'cookiejar/jar'
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.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.2.0
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
@@ -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