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.
@@ -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