dwaite-cookiejar 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/cookiejar/jar.rb CHANGED
@@ -1,46 +1,46 @@
1
1
  require 'cookiejar/cookie'
2
-
3
- # A cookie store for client side usage.
4
- # - Enforces cookie validity rules
5
- # - Returns just the cookies valid for a given URI
6
- # - Handles expiration of cookies
7
- # - Allows for persistence of cookie data (with or without sessoin)
8
- #
9
- #--
10
- # Internal format:
11
- #
12
- # Internally, the data structure is a set of nested hashes.
13
- # Domain Level:
14
- # At the domain level, the hashes are of individual domains,
15
- # down-cased and without any leading period. For instance, imagine cookies
16
- # for .foo.com, .bar.com, and .auth.bar.com:
17
- #
18
- # {
19
- # "foo.com" : <host data>,
20
- # "bar.com" : <host data>,
21
- # "auth.bar.com" : <host data>
22
- # }
23
- # Lookups are done both for the matching entry, and for an entry without
24
- # the first segment up to the dot, ie. for /^\.?[^\.]+\.(.*)$/.
25
- # A lookup of auth.bar.com would match both bar.com and
26
- # auth.bar.com, but not entries for com or www.auth.bar.com.
27
- #
28
- # Host Level:
29
- # Entries are in an hash, with keys of the path and values of a hash of
30
- # cookie names to cookie object
31
- #
32
- # {
33
- # "/" : {"session" : <Cookie>, "cart_id" : <Cookie>}
34
- # "/protected" : {"authentication" : <Cookie>}
35
- # }
36
- #
37
- # Paths are given a straight prefix string comparison to match.
38
- # Further filters <secure, http only, ports> are not represented in this
39
- # heirarchy.
40
- #
41
- # Cookies returned are ordered solely by specificity (length) of the
42
- # path.
2
+
43
3
  module CookieJar
4
+ # A cookie store for client side usage.
5
+ # - Enforces cookie validity rules
6
+ # - Returns just the cookies valid for a given URI
7
+ # - Handles expiration of cookies
8
+ # - Allows for persistence of cookie data (with or without session)
9
+ #
10
+ #--
11
+ # Internal format:
12
+ #
13
+ # Internally, the data structure is a set of nested hashes.
14
+ # Domain Level:
15
+ # At the domain level, the hashes are of individual domains,
16
+ # down-cased and without any leading period. For instance, imagine cookies
17
+ # for .foo.com, .bar.com, and .auth.bar.com:
18
+ #
19
+ # {
20
+ # "foo.com" : <host data>,
21
+ # "bar.com" : <host data>,
22
+ # "auth.bar.com" : <host data>
23
+ # }
24
+ # Lookups are done both for the matching entry, and for an entry without
25
+ # the first segment up to the dot, ie. for /^\.?[^\.]+\.(.*)$/.
26
+ # A lookup of auth.bar.com would match both bar.com and
27
+ # auth.bar.com, but not entries for com or www.auth.bar.com.
28
+ #
29
+ # Host Level:
30
+ # Entries are in an hash, with keys of the path and values of a hash of
31
+ # cookie names to cookie object
32
+ #
33
+ # {
34
+ # "/" : {"session" : <Cookie>, "cart_id" : <Cookie>}
35
+ # "/protected" : {"authentication" : <Cookie>}
36
+ # }
37
+ #
38
+ # Paths are given a straight prefix string comparison to match.
39
+ # Further filters <secure, http only, ports> are not represented in this
40
+ # heirarchy.
41
+ #
42
+ # Cookies returned are ordered solely by specificity (length) of the
43
+ # path.
44
44
  class Jar
45
45
  def initialize
46
46
  @domains = {}
@@ -49,13 +49,71 @@ module CookieJar
49
49
  # Given a request URI and a literal Set-Cookie header value, attempt to
50
50
  # add the cookie to the cookie store.
51
51
  #
52
- # returns the Cookie object on success, otherwise raises an
52
+ # Returns the Cookie object on success, otherwise raises an
53
53
  # InvalidCookieError
54
54
  def set_cookie request_uri, cookie_header_value
55
55
  cookie = Cookie.from_set_cookie request_uri, cookie_header_value
56
- domain_paths = find_or_add_domain_for_cookie cookie
57
- add_cookie_to_path(domain_paths,cookie)
56
+ add_cookie cookie
57
+ end
58
+
59
+ # Add a pre-existing cookie object to the jar.
60
+ def add_cookie cookie
61
+ domain_paths = find_or_add_domain_for_cookie cookie
62
+ add_cookie_to_path domain_paths, cookie
58
63
  cookie
64
+ end
65
+
66
+ # Return an array of all cookie objects in the jar
67
+ def to_a
68
+ result = []
69
+ @domains.values.each do |paths|
70
+ paths.values.each do |cookies|
71
+ cookies.values.inject result, :<<
72
+ end
73
+ end
74
+ result
75
+ end
76
+
77
+ def to_json *a
78
+ {
79
+ 'json_class' => self.class.name,
80
+ 'cookies' => (to_a.to_json *a)
81
+ }.to_json *a
82
+ end
83
+
84
+ # Create a new Jar from a JSON-backed hash.
85
+ def self.json_create o
86
+ if o.is_a? Hash
87
+ o = o['cookies']
88
+ end
89
+ cookies = o.inject [] do |result, cookie_json|
90
+ result << (Cookie.json_create cookie_json)
91
+ end
92
+ self.from_a cookies
93
+ end
94
+
95
+ # Create a new Jar from an array of Cookie objects
96
+ def self.from_a cookies
97
+ jar = new
98
+ cookies.each do |cookie|
99
+ jar.add_cookie cookie
100
+ end
101
+ jar
102
+ end
103
+
104
+ # Look through the jar for any cookies which have passed their expiration
105
+ # date. Optionally, you can pass true to expire any session cookies as
106
+ # well
107
+ def expire_cookies session = false
108
+ @domains.delete_if do |domain, paths|
109
+ paths.delete_if do |path, cookies|
110
+ cookies.delete_if do |cookie_name, cookie|
111
+ cookie.is_expired? || (session && cookie.is_session?)
112
+ end
113
+ cookies.empty?
114
+ end
115
+ paths.empty?
116
+ end
59
117
  end
60
118
 
61
119
  # Given a request URI, return a sorted list of Cookie objects. Cookies
@@ -64,8 +122,8 @@ module CookieJar
64
122
  #
65
123
  # optional arguments are
66
124
  # - :script - if set, cookies set to be HTTP-only will be ignored
67
- def get_cookies request_uri, args = {}
68
- uri = request_uri.is_a?(URI)? request_uri : URI.parse(request_uri)
125
+ def get_cookies request_uri, args = { }
126
+ uri = to_uri request_uri
69
127
  hosts = Cookie.compute_search_domains uri
70
128
 
71
129
  results = []
@@ -73,11 +131,9 @@ module CookieJar
73
131
  domain = find_domain host
74
132
  domain.each do |path, cookies|
75
133
  if uri.path.start_with? path
76
- results += cookies.select do |name, cookie|
134
+ results += cookies.values.select do |cookie|
77
135
  cookie.should_send? uri, args[:script]
78
- end.collect do |name, cookie|
79
- cookie
80
- end
136
+ end
81
137
  end
82
138
  end
83
139
  end
@@ -87,21 +143,28 @@ module CookieJar
87
143
  end
88
144
  end
89
145
 
90
- # Given a request URI, return a sorted array of Cookie headers, in the
91
- # format ['Cookie', '<Header Value>']. Cookies will be in order per
92
- # RFC 2965 - sorted by longest path length, but otherwise unordered.
146
+ # Given a request URI, return a string Cookie header.Cookies will be in
147
+ # order per RFC 2965 - sorted by longest path length, but otherwise
148
+ # unordered.
93
149
  #
94
150
  # optional arguments are
95
151
  # - :script - if set, cookies set to be HTTP-only will be ignored
96
- def get_cookie_headers request_uri, args = {}
152
+ def get_cookie_header request_uri, args = { }
97
153
  cookies = get_cookies request_uri, args
98
154
  cookies.map do |cookie|
99
- ['Cookie', "#{cookie.name}=#{cookie.value}"]
100
- end
155
+ cookie.to_s
156
+ end.join ";"
101
157
  end
102
158
 
159
+ # Given a request URI, return a string containing the value of a possible
160
+ # Cookie header. Cookies will be in order per RFC 2965 - sorted by
161
+ # longest path length, but otherwise unordered.
103
162
  protected
104
163
 
164
+ def to_uri request_uri
165
+ (request_uri.is_a? URI)? request_uri : (URI.parse request_uri)
166
+ end
167
+
105
168
  def find_domain host
106
169
  @domains[host] || {}
107
170
  end
@@ -110,7 +173,7 @@ module CookieJar
110
173
  @domains[cookie.domain] ||= {}
111
174
  end
112
175
 
113
- def add_cookie_to_path (paths, cookie)
176
+ def add_cookie_to_path paths, cookie
114
177
  path_entry = (paths[cookie.path] ||= {})
115
178
  path_entry[cookie.name] = cookie
116
179
  end
data/test/cookie_test.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'cookiejar'
2
+ require 'rubygems'
2
3
 
3
4
  include CookieJar
4
5
 
@@ -43,233 +44,32 @@ describe Cookie do
43
44
  cookie.secure.should be_true
44
45
  end
45
46
  end
46
- describe '.validate_cookie' do
47
- localaddr = 'http://localhost/foo/bar/'
48
- it "should fail if version unset" do
49
- lambda do
50
- unversioned = Cookie.from_set_cookie localaddr, 'foo=bar'
51
- unversioned.instance_variable_set :@version, nil
52
- Cookie.validate_cookie localaddr, unversioned
53
- end.should raise_error InvalidCookieError
54
- end
55
- it "should fail if the path is more specific" do
56
- lambda do
57
- subdirred = Cookie.from_set_cookie localaddr, 'foo=bar;path=/foo/bar/baz'
58
- # validate_cookie localaddr, subdirred
59
- end.should raise_error InvalidCookieError
60
- end
61
- it "should fail if the path is different than the request" do
62
- lambda do
63
- difdirred = Cookie.from_set_cookie localaddr, 'foo=bar;path=/baz/'
64
- # validate_cookie localaddr, difdirred
65
- end.should raise_error InvalidCookieError
66
- end
67
- it "should fail if the domain has no dots" do
68
- lambda do
69
- nodot = Cookie.from_set_cookie 'http://zero/', 'foo=bar;domain=zero'
70
- # validate_cookie 'http://zero/', nodot
71
- end.should raise_error InvalidCookieError
72
- end
73
- it "should fail for explicit localhost" do
74
- lambda do
75
- localhost = Cookie.from_set_cookie localaddr, 'foo=bar;domain=localhost'
76
- # validate_cookie localaddr, localhost
77
- end.should raise_error InvalidCookieError
78
- end
79
- it "should fail for mismatched domains" do
80
- lambda do
81
- foobar = Cookie.from_set_cookie 'http://www.foo.com/', 'foo=bar;domain=bar.com'
82
- # validate_cookie 'http://www.foo.com/', foobar
83
- end.should raise_error InvalidCookieError
84
- end
85
- it "should fail for domains more than one level up" do
86
- lambda do
87
- xyz = Cookie.from_set_cookie 'http://x.y.z.com/', 'foo=bar;domain=z.com'
88
- # validate_cookie 'http://x.y.z.com/', xyz
89
- end.should raise_error InvalidCookieError
90
- end
91
- it "should fail for setting subdomain cookies" do
92
- lambda do
93
- subdomain = Cookie.from_set_cookie 'http://foo.com/', 'foo=bar;domain=auth.foo.com'
94
- # validate_cookie 'http://foo.com/', subdomain
95
- end.should raise_error InvalidCookieError
96
- end
97
- it "should handle a normal implicit internet cookie" do
98
- normal = Cookie.from_set_cookie 'http://foo.com/', 'foo=bar'
99
- Cookie.validate_cookie('http://foo.com/', normal).should be_true
100
- end
101
- it "should handle a normal implicit localhost cookie" do
102
- localhost = Cookie.from_set_cookie 'http://localhost/', 'foo=bar'
103
- Cookie.validate_cookie('http://localhost/', localhost).should be_true
104
- end
105
- it "should handle an implicit IP address cookie" do
106
- ipaddr = Cookie.from_set_cookie 'http://127.0.0.1/', 'foo=bar'
107
- Cookie.validate_cookie('http://127.0.0.1/', ipaddr).should be_true
108
- end
109
- it "should handle an explicit domain on an internet site" do
110
- explicit = Cookie.from_set_cookie 'http://foo.com/', 'foo=bar;domain=.foo.com'
111
- Cookie.validate_cookie('http://foo.com/', explicit).should be_true
112
- end
113
- it "should handle setting a cookie explicitly on a superdomain" do
114
- superdomain = Cookie.from_set_cookie 'http://auth.foo.com/', 'foo=bar;domain=.foo.com'
115
- Cookie.validate_cookie('http://foo.com/', superdomain).should be_true
116
- end
117
- it "should handle explicitly setting a cookie" do
118
- explicit = Cookie.from_set_cookie 'http://foo.com/bar/', 'foo=bar;path=/bar/'
119
- Cookie.validate_cookie('http://foo.com/bar/', explicit)
120
- end
121
- it "should handle setting a cookie on a higher path" do
122
- higher = Cookie.from_set_cookie 'http://foo.com/bar/baz/', 'foo=bar;path=/bar/'
123
- Cookie.validate_cookie('http://foo.com/bar/baz/', higher)
124
- end
125
- end
126
- describe '.cookie_base_path' do
127
- it "should leave '/' alone" do
128
- Cookie.cookie_base_path('/').should == '/'
129
- end
130
- it "should strip off everything after the last '/'" do
131
- Cookie.cookie_base_path('/foo/bar/baz').should == '/foo/bar/'
132
- end
133
- it "should handle query parameters and fragments with slashes" do
134
- Cookie.cookie_base_path('/foo/bar?query=a/b/c#fragment/b/c').should == '/foo/'
135
- end
136
- it "should handle URI objects" do
137
- Cookie.cookie_base_path(URI.parse('http://www.foo.com/bar/')).should == '/bar/'
138
- end
139
- it "should preserve case" do
140
- Cookie.cookie_base_path("/BaR/").should == '/BaR/'
141
- end
142
- end
143
- describe '.determine_cookie_path' do
144
- it "should use the requested path when none is specified for the cookie" do
145
- Cookie.determine_cookie_path('http://foo.com/', nil).should == '/'
146
- Cookie.determine_cookie_path('http://foo.com/bar/baz', '').should == '/bar/'
147
- end
148
- it "should handle URI objects" do
149
- Cookie.determine_cookie_path(URI.parse('http://foo.com/bar/'), '').should == '/bar/'
150
- end
151
- it "should handle Cookie objects" do
152
- cookie = Cookie.from_set_cookie('http://foo.com/', "name=value;path=/")
153
- Cookie.determine_cookie_path('http://foo.com/', cookie).should == '/'
154
- end
155
- it "should ignore the request when a path is specified" do
156
- Cookie.determine_cookie_path('http://foo.com/ignorable/path', '/path/').should == '/path/'
157
- end
158
- end
159
- describe '.compute_search_domains' do
160
- it "should handle subdomains" do
161
- Cookie.compute_search_domains('http://www.auth.foo.com/').should ==
162
- ['www.auth.foo.com', '.www.auth.foo.com', '.auth.foo.com']
163
- end
164
- it "should handle root domains" do
165
- Cookie.compute_search_domains('http://foo.com/').should ==
166
- ['foo.com', '.foo.com']
167
- end
168
- it "should handle IP addresses" do
169
- Cookie.compute_search_domains('http://127.0.0.1/').should ==
170
- ['127.0.0.1']
171
- end
172
- it "should handle local addresses" do
173
- Cookie.compute_search_domains('http://zero/').should ==
174
- ['zero.local', '.zero.local', '.local']
175
- end
176
- end
177
- describe '.determine_cookie_domain' do
178
- it "should add a dot to the front of domains" do
179
- Cookie.determine_cookie_domain('http://foo.com/', 'foo.com').should == '.foo.com'
180
- end
181
- it "should not add a second dot if one present" do
182
- Cookie.determine_cookie_domain('http://foo.com/', '.foo.com').should == '.foo.com'
183
- end
184
- it "should handle Cookie objects" do
185
- c = Cookie.from_set_cookie('http://foo.com/', "foo=bar;domain=foo.com")
186
- Cookie.determine_cookie_domain('http://foo.com/', c).should == '.foo.com'
187
- end
188
- it "should handle URI objects" do
189
- Cookie.determine_cookie_domain(URI.parse('http://foo.com/'), '.foo.com').should == '.foo.com'
190
- end
191
- it "should use an exact hostname when no domain specified" do
192
- Cookie.determine_cookie_domain('http://foo.com/', '').should == 'foo.com'
193
- end
194
- it "should leave IPv4 addresses alone" do
195
- Cookie.determine_cookie_domain('http://127.0.0.1/', '127.0.0.1').should == '127.0.0.1'
196
- end
197
- it "should leave IPv6 addresses alone" do
198
- ['2001:db8:85a3::8a2e:370:7334', '::ffff:192.0.2.128'].each do |value|
199
- Cookie.determine_cookie_domain("http://[#{value}]/", value).should == value
47
+ begin
48
+ require 'json'
49
+ describe ".to_json" do
50
+ it "should serialize a cookie to JSON" do
51
+ c = Cookie.from_set_cookie 'https://localhost/', 'foo=bar;secure;expires=Fri, September 11 2009 18:10:00 -0700'
52
+ json = c.to_json
53
+ json.should be_a String
200
54
  end
201
55
  end
202
- end
203
- describe ".effective_host" do
204
- it "should leave proper domains the same" do
205
- ['google.com', 'www.google.com', 'google.com.'].each do |value|
206
- Cookie.effective_host(value).should == value
56
+ describe ".json_create" do
57
+ it "should deserialize JSON to a cookie" do
58
+ json = "{\"name\":\"foo\",\"value\":\"bar\",\"domain\":\"localhost.local\",\"path\":\"\\/\",\"created_at\":\"2009-09-11 12:51:03 -0600\",\"expiry\":\"2009-09-11 19:10:00 -0600\",\"secure\":true}"
59
+ hash = JSON.parse json
60
+ c = Cookie.json_create hash
61
+ CookieValidation.validate_cookie 'https://localhost/', c
207
62
  end
208
- end
209
- it "should handle a URI object" do
210
- Cookie.effective_host(URI.parse('http://example.com/')).should == 'example.com'
211
- end
212
- it "should add a local suffix on unqualified hosts" do
213
- Cookie.effective_host('localhost').should == 'localhost.local'
214
- end
215
- it "should leave IPv4 addresses alone" do
216
- Cookie.effective_host('127.0.0.1').should == '127.0.0.1'
217
- end
218
- it "should leave IPv6 addresses alone" do
219
- ['2001:db8:85a3::8a2e:370:7334', ':ffff:192.0.2.128'].each do |value|
220
- Cookie.effective_host(value).should == value
63
+ it "should automatically deserialize to a cookie" do
64
+ json = "{\"json_class\":\"CookieJar::Cookie\",\"name\":\"foo\",\"value\":\"bar\",\"domain\":\"localhost.local\",\"path\":\"\\/\",\"created_at\":\"2009-09-11 12:51:03 -0600\",\"expiry\":\"2009-09-11 19:10:00 -0600\",\"secure\":true}"
65
+ c = JSON.parse json
66
+ c.should be_a Cookie
67
+ CookieValidation.validate_cookie 'https://localhost/', c
221
68
  end
222
69
  end
223
- it "should lowercase addresses" do
224
- Cookie.effective_host('FOO.COM').should == 'foo.com'
225
- end
226
- end
227
- describe '.domains_match' do
228
- it "should handle exact matches" do
229
- Cookie.domains_match('foo', 'foo').should be_true
230
- Cookie.domains_match('foo.com', 'foo.com').should be_true
231
- Cookie.domains_match('127.0.0.1', '127.0.0.1').should be_true
232
- Cookie.domains_match('::ffff:192.0.2.128', '::ffff:192.0.2.128').should be_true
233
- end
234
- it "should handle matching a superdomain" do
235
- Cookie.domains_match('auth.foo.com', 'foo.com').should be_true
236
- Cookie.domains_match('x.y.z.foo.com', 'y.z.foo.com').should be_true
237
- end
238
- it "should not match superdomains, or illegal domains" do
239
- Cookie.domains_match('x.y.z.foo.com', 'z.foo.com').should be_false
240
- Cookie.domains_match('foo.com', 'com').should be_false
241
- end
242
- it "should not match domains with and without a dot suffix together" do
243
- Cookie.domains_match('foo.com.', 'foo.com').should be_false
244
- end
245
- end
246
- describe '.hostname_reach' do
247
- it "should find the next highest subdomain" do
248
- {'www.google.com' => 'google.com', 'auth.corp.companyx.com' => 'corp.companyx.com'}.each do |entry|
249
- Cookie.hostname_reach(entry[0]).should == entry[1]
250
- end
251
- end
252
- it "should handle domains with suffixed dots" do
253
- Cookie.hostname_reach('www.google.com.').should == 'google.com.'
254
- end
255
- it "should return nil for a root domain" do
256
- Cookie.hostname_reach('github.com').should be_nil
257
- end
258
- it "should return 'local' for a local domain" do
259
- ['foo.local', 'foo.local.'].each do |hostname|
260
- Cookie.hostname_reach(hostname).should == 'local'
261
- end
262
- end
263
- it "should handle mixed-case '.local'" do
264
- Cookie.hostname_reach('foo.LOCAL').should == 'local'
265
- end
266
- it "should return nil for an IPv4 address" do
267
- Cookie.hostname_reach('127.0.0.1').should be_nil
268
- end
269
- it "should return nil for IPv6 addresses" do
270
- ['2001:db8:85a3::8a2e:370:7334', '::ffff:192.0.2.128'].each do |value|
271
- Cookie.hostname_reach(value).should be_nil
272
- end
70
+ rescue LoadError
71
+ it "does not appear the JSON library is installed" do
72
+ raise "please install the JSON library"
273
73
  end
274
74
  end
275
- end
75
+ end