dwaite-cookiejar 0.1.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.
@@ -0,0 +1,95 @@
1
+ require 'time'
2
+ require 'cookiejar/cookie_logic'
3
+ module CookieJar
4
+ class Cookie
5
+ include CookieLogic
6
+
7
+ attr_reader :name, :value
8
+ attr_reader :created_at
9
+ attr_reader :expiry
10
+ attr_reader :domain, :path
11
+ attr_reader :secure, :http_only
12
+ attr_reader :version
13
+ # Currently unused attributes for RFC 2965 cookies
14
+ # attr_reader :comment, :comment_url
15
+ # attr_reader :discard
16
+ # attr_reader :ports
17
+
18
+ def expires_at
19
+ if expiry.nil?
20
+ nil
21
+ elsif expiry.is_a? Time
22
+ expiry
23
+ else
24
+ Time.now + expiry
25
+ end
26
+ end
27
+ def max_age
28
+ if expiry.is_a? Integer
29
+ expiry
30
+ else
31
+ expiry - Time.now
32
+ end
33
+ end
34
+ def port
35
+ nil
36
+ end
37
+ def initialize(uri, *params)
38
+ case params.length
39
+ when 1
40
+ args = params[0]
41
+ when 2
42
+ args = {:name => args[0], :value => args[1]}
43
+ else
44
+ raise ArgumentError.new "wrong number of arguments (expected 1 or 2)"
45
+ end
46
+
47
+ @domain = determine_cookie_domain(uri, args[:domain])
48
+ @expiry = args[:max_age] || args[:expires_at] || nil
49
+ @path = determine_cookie_path(uri, args[:path])
50
+ @secure = args[:secure] || false
51
+ @http_only = args[:http_only] ||false
52
+ @name = args[:name]
53
+ @value = args[:value]
54
+ @version = args[:version]
55
+ @created_at = DateTime.now
56
+ end
57
+
58
+ PARAM1 = /\A(#{PATTERN::TOKEN})(?:=#{PATTERN::VALUE1})?\Z/
59
+ # PARAM2 = /\A(#{PATTERN::TOKEN})(?:=#{PATTERN::VALUE2})?\Z/
60
+
61
+ def self.from_set_cookie(request_uri, set_cookie_value)
62
+ args = {}
63
+ params=set_cookie_value.split(/;\s*/)
64
+ params.each do |param|
65
+ result = PARAM1.match param
66
+ if (!result)
67
+ raise InvalidCookieError.new("Invalid cookie parameter in cookie '#{set_cookie_value}'")
68
+ end
69
+ key = result[1].upcase
70
+ keyvalue = result[2] || result[3]
71
+ case key
72
+ when 'EXPIRES'
73
+ args[:expires_at] = DateTime.parse(keyvalue)
74
+ when 'DOMAIN'
75
+ args[:domain] = keyvalue.downcase
76
+ when 'PATH'
77
+ args[:path] = keyvalue
78
+ when 'SECURE'
79
+ args[:secure] = true
80
+ when 'HTTPONLY'
81
+ args[:http_only] = true
82
+ else
83
+ args[:name] = result[1]
84
+ args[:value] = keyvalue
85
+ end
86
+ end
87
+ args[:version] = 0
88
+ Cookie.new(request_uri, args)
89
+ end
90
+
91
+ def to_s
92
+ %Q^#{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}^
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,4 @@
1
+ module CookieJar
2
+ class CookieError < StandardError; end
3
+ class InvalidCookieError < CookieError; end
4
+ end
@@ -0,0 +1,202 @@
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
+ # Check whether a cookie meets all of the rules to be created, based on
113
+ # its internal settings and the URI it came from.
114
+ #
115
+ # returns true on success, but will raise an InvalidCookieError on failure
116
+ # with an appropriate error message
117
+ def validate_cookie request_uri, cookie
118
+ uri = request_uri.is_a?(URI) ? request_uri : URI.parse(request_uri)
119
+
120
+ request_host = effective_host uri.host
121
+ request_path = uri.path
122
+ request_secure = (uri.scheme == 'https')
123
+ cookie_host = cookie.domain
124
+ cookie_path = cookie.path
125
+
126
+ # From RFC 2965, Section 3.3.2 Rejecting Cookies
127
+
128
+ # A user agent rejects (SHALL NOT store its information) if the
129
+ # Version attribute is missing. Note that the legacy Set-Cookie
130
+ # directive will result in an implicit version 0.
131
+ unless cookie.version
132
+ raise InvalidCookieError, "Cookie version not supplied (or implicit with Set-Cookie)"
133
+ end
134
+
135
+ # The value for the Path attribute is not a prefix of the request-URI
136
+ unless request_path.start_with? cookie_path
137
+ raise InvalidCookieError, "Cookie path should match or be a subset of the request path"
138
+ end
139
+
140
+ # The value for the Domain attribute contains no embedded dots, and the value is not .local
141
+ # Note: we also allow IPv4 and IPv6 addresses
142
+ unless cookie_host =~ IPADDR || cookie_host =~ /.\../ || cookie_host == '.local'
143
+ raise InvalidCookieError, "Cookie domain format is not legal"
144
+ end
145
+
146
+ # The effective host name that derives from the request-host does
147
+ # not domain-match the Domain attribute.
148
+ #
149
+ # The request-host is a HDN (not IP address) and has the form HD,
150
+ # where D is the value of the Domain attribute, and H is a string
151
+ # that contains one or more dots.
152
+ effective_host = effective_host uri
153
+ unless domains_match effective_host, cookie_host
154
+ raise InvalidCookieError, "Cookie domain is inappropriate based on request hostname"
155
+ end
156
+
157
+ # The Port attribute has a "port-list", and the request-port was
158
+ # not in the list.
159
+ if cookie.port.to_a.length != 0
160
+ unless cookie.port.to_a.find_index uri.port
161
+ raise InvalidCookieError, "incoming request port does not match cookie port(s)"
162
+ end
163
+ end
164
+
165
+ # Note: 'secure' is not explicitly defined as an SSL channel, and no
166
+ # test is defined around validity and the 'secure' attribute
167
+ true
168
+ end
169
+
170
+ # Given a URI, compute the relevant search domains for pre-existing
171
+ # cookies. This includes all the valid dotted forms for a named or IP
172
+ # domains.
173
+ def compute_search_domains request_uri
174
+ uri = request_uri.is_a?(URI) ? request_uri : URI.parse(request_uri)
175
+ host = effective_host uri
176
+ result = [host]
177
+ if (host !~ IPADDR)
178
+ result << ".#{host}"
179
+ end
180
+ base = hostname_reach host
181
+ if (base)
182
+ result << ".#{base}"
183
+ end
184
+ result
185
+ end
186
+
187
+ # Return true if (given a URI, a cookie object and other options) a cookie
188
+ # should be sent to a host. Note that this currently ignores domain.
189
+ #
190
+ # The third option, 'script', indicates that cookies with the 'http only'
191
+ # extension should be ignored
192
+ def send_cookie? uri, cookie, script
193
+ # cookie path must start with the uri, it must not be a secure cookie being sent over http,
194
+ # and it must not be a http_only cookie sent to a script
195
+ path_match = uri.path.start_with? cookie.path
196
+ secure_match = !(cookie.secure && uri.scheme == 'http')
197
+ script_match = !(script && cookie.http_only)
198
+ expiry_match = cookie.expires_at.nil? || cookie.expires_at > Time.now
199
+ path_match && secure_match && script_match && expiry_match
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,125 @@
1
+ require 'cookiejar/cookie_logic'
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.
43
+ module CookieJar
44
+ class Jar
45
+ include CookieLogic
46
+ def initialize
47
+ @domains = {}
48
+ end
49
+
50
+ # Given a request URI and a literal Set-Cookie header value, attempt to
51
+ # add the cookie to the cookie store.
52
+ #
53
+ # returns the Cookie object on success, otherwise raises an
54
+ # InvalidCookieError
55
+ 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
+ if (validate_cookie uri, cookie)
60
+ domain_paths = find_or_add_domain_for_cookie(cookie.domain)
61
+ add_cookie_to_path(domain_paths,cookie)
62
+ cookie
63
+ end
64
+ end
65
+
66
+ # Given a request URI, return a sorted list of Cookie objects. Cookies
67
+ # will be in order per RFC 2965 - sorted by longest path length, but
68
+ # otherwise unordered.
69
+ #
70
+ # optional arguments are
71
+ # - :script - if set, cookies set to be HTTP-only will be ignored
72
+ def get_cookies request_uri, args = {}
73
+ uri = request_uri.is_a?(URI)? request_uri : URI.parse(request_uri)
74
+ hosts = compute_search_domains uri
75
+
76
+ results = []
77
+ hosts.each do |host|
78
+ domain = find_domain_for_cookie host
79
+ domain.each do |path, cookies|
80
+ if uri.path.start_with? path
81
+ results += cookies.select do |name, cookie|
82
+ send_cookie? uri, cookie, args[:script]
83
+ end.collect do |name, cookie|
84
+ cookie
85
+ end
86
+ end
87
+ end
88
+ end
89
+ #Sort by path length, longest first
90
+ results.sort do |lhs, rhs|
91
+ rhs.path.length <=> lhs.path.length
92
+ end
93
+ end
94
+
95
+ # Given a request URI, return a sorted array of Cookie headers, in the
96
+ # format ['Cookie', '<Header Value>']. Cookies will be in order per
97
+ # RFC 2965 - sorted by longest path length, but otherwise unordered.
98
+ #
99
+ # optional arguments are
100
+ # - :script - if set, cookies set to be HTTP-only will be ignored
101
+ def get_cookie_headers request_uri, args = {}
102
+ cookies = get_cookies request_uri, args
103
+ cookies.map do |cookie|
104
+ ['Cookie', "#{cookie.name}=#{cookie.value}"]
105
+ end
106
+ end
107
+
108
+ protected
109
+
110
+ def find_domain_for_cookie domain
111
+ domain = effective_host domain
112
+ @domains[domain] || {}
113
+ end
114
+
115
+ def find_or_add_domain_for_cookie(domain)
116
+ domain = effective_host domain
117
+ @domains[domain] ||= {}
118
+ end
119
+
120
+ def add_cookie_to_path (paths, cookie)
121
+ path_entry = (paths[cookie.path] ||= {})
122
+ path_entry[cookie.name] = cookie
123
+ end
124
+ end
125
+ end
data/lib/cookiejar.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'cookiejar/cookie'
2
+ require 'cookiejar/jar'
@@ -0,0 +1,236 @@
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 '.validate_cookie' do
140
+ localaddr = 'http://localhost/foo/bar/'
141
+ it "should fail if version unset" do
142
+ unversioned = Cookie.new localaddr, :name => 'foo', :value => 'bar', :version => nil
143
+ lambda do
144
+ validate_cookie localaddr, unversioned
145
+ end.should raise_error InvalidCookieError
146
+ end
147
+ it "should fail if the path is more specific" do
148
+ subdirred = Cookie.new localaddr, :name => 'foo', :value => 'bar', :version => 0, :path => '/foo/bar/baz'
149
+ lambda do
150
+ validate_cookie localaddr, subdirred
151
+ end.should raise_error InvalidCookieError
152
+ end
153
+ it "should fail if the path is different than the request" do
154
+ difdirred = Cookie.new localaddr, :name => 'foo', :value => 'bar', :version => 0, :path => '/baz/'
155
+ lambda do
156
+ validate_cookie localaddr, difdirred
157
+ end.should raise_error InvalidCookieError
158
+ end
159
+ it "should fail if the domain has no dots" do
160
+ nodot = Cookie.new 'http://zero/', :name => 'foo', :value => 'bar', :version => 0, :domain => 'zero'
161
+ lambda do
162
+ validate_cookie 'http://zero/', nodot
163
+ end.should raise_error InvalidCookieError
164
+ end
165
+ it "should fail for explicit localhost" do
166
+ localhost = Cookie.new localaddr, :name => 'foo', :value => 'bar', :version => 0, :domain => 'localhost'
167
+ lambda do
168
+ validate_cookie localaddr, localhost
169
+ end.should raise_error InvalidCookieError
170
+ end
171
+ it "should fail for mismatched domains" do
172
+ foobar = Cookie.new 'http://www.foo.com/', :name => 'foo', :value => 'bar', :version => 0, :domain => 'bar.com'
173
+ lambda do
174
+ validate_cookie 'http://www.foo.com/', foobar
175
+ end.should raise_error InvalidCookieError
176
+ end
177
+ it "should fail for domains more than one level up" do
178
+ xyz = Cookie.new 'http://x.y.z.com/', :name => 'foo', :value => 'bar', :version => 0, :domain => 'z.com'
179
+ lambda do
180
+ validate_cookie 'http://x.y.z.com/', xyz
181
+ end.should raise_error InvalidCookieError
182
+ end
183
+ it "should fail for setting subdomain cookies" do
184
+ subdomain = Cookie.new 'http://foo.com/', :name => 'foo', :value => 'bar', :version => 0, :domain => 'auth.foo.com'
185
+ lambda do
186
+ validate_cookie 'http://foo.com/', subdomain
187
+ end.should raise_error InvalidCookieError
188
+ end
189
+ it "should handle a normal implicit internet cookie" do
190
+ normal = Cookie.new 'http://foo.com/', :name => 'foo', :value => 'bar', :version => 0
191
+ validate_cookie('http://foo.com/', normal).should be_true
192
+ end
193
+ it "should handle a normal implicit localhost cookie" do
194
+ localhost = Cookie.new 'http://localhost/', :name => 'foo', :value => 'bar', :version => 0
195
+ validate_cookie('http://localhost/', localhost).should be_true
196
+ end
197
+ it "should handle an implicit IP address cookie" do
198
+ ipaddr = Cookie.new 'http://127.0.0.1/', :name => 'foo', :value => 'bar', :version => 0
199
+ validate_cookie('http://127.0.0.1/', ipaddr).should be_true
200
+ end
201
+ it "should handle an explicit domain on an internet site" do
202
+ explicit = Cookie.new 'http://foo.com/', :name => 'foo', :value => 'bar', :version => 0, :domain => '.foo.com'
203
+ validate_cookie('http://foo.com/', explicit).should be_true
204
+ end
205
+ it "should handle setting a cookie explicitly on a superdomain" do
206
+ superdomain = Cookie.new 'http://auth.foo.com/', :name => 'foo', :value => 'bar', :version => 0, :domain => '.foo.com'
207
+ validate_cookie('http://foo.com/', superdomain).should be_true
208
+ end
209
+ it "should handle explicitly setting a cookie" do
210
+ explicit = Cookie.new 'http://foo.com/bar/', :name => 'foo', :value => 'bar', :version => 0, :path => '/bar/'
211
+ validate_cookie('http://foo.com/bar/', explicit)
212
+ end
213
+ it "should handle setting a cookie on a higher path" do
214
+ higher = Cookie.new 'http://foo.com/bar/baz/', :name => 'foo', :value => 'bar', :version => 0, :path => '/bar/'
215
+ validate_cookie('http://foo.com/bar/baz/', higher)
216
+ end
217
+ end
218
+ describe '.compute_search_domains' do
219
+ it "should handle subdomains" do
220
+ compute_search_domains('http://www.auth.foo.com/').should ==
221
+ ['www.auth.foo.com', '.www.auth.foo.com', '.auth.foo.com']
222
+ end
223
+ it "should handle root domains" do
224
+ compute_search_domains('http://foo.com/').should ==
225
+ ['foo.com', '.foo.com']
226
+ end
227
+ it "should handle IP addresses" do
228
+ compute_search_domains('http://127.0.0.1/').should ==
229
+ ['127.0.0.1']
230
+ end
231
+ it "should handle local addresses" do
232
+ compute_search_domains('http://zero/').should ==
233
+ ['zero.local', '.zero.local', '.local']
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,24 @@
1
+ require 'cookiejar'
2
+ include CookieJar
3
+
4
+ NETSCAPE_SPEC_SET_COOKIE_HEADERS =
5
+ ['CUSTOMER=WILE_E_COYOTE; path=/; expires=Wednesday, 09-Nov-99 23:12:40 GMT',
6
+ 'PART_NUMBER=ROCKET_LAUNCHER_0001; path=/',
7
+ 'SHIPPING=FEDEX; path=/foo',
8
+ 'PART_NUMBER=ROCKET_LAUNCHER_0001; path=/',
9
+ 'PART_NUMBER=RIDING_ROCKET_0023; path=/ammo']
10
+
11
+ describe Cookie do
12
+ describe "#from_set_cookie" do
13
+ it "should handle cookies from the netscape spec" do
14
+ NETSCAPE_SPEC_SET_COOKIE_HEADERS.each do |header|
15
+ cookie = Cookie.from_set_cookie 'http://localhost', header
16
+ end
17
+ end
18
+ it "should give back the input names and values" do
19
+ cookie = Cookie.from_set_cookie 'http://localhost/', 'foo=bar'
20
+ cookie.name.should == 'foo'
21
+ cookie.value.should == 'bar'
22
+ end
23
+ end
24
+ end
data/test/jar_test.rb ADDED
@@ -0,0 +1,54 @@
1
+ require 'cookiejar'
2
+ require 'yaml'
3
+ include CookieJar
4
+
5
+ describe Jar do
6
+ describe '.setCookie' do
7
+ it "should allow me to set a cookie" do
8
+ jar = Jar.new
9
+ jar.set_cookie 'http://foo.com/', 'foo=bar'
10
+ end
11
+ it "should allow me to set multiple cookies" do
12
+ jar = Jar.new
13
+ jar.set_cookie 'http://foo.com/', 'foo=bar'
14
+ jar.set_cookie 'http://foo.com/', 'bar=baz'
15
+ jar.set_cookie 'http://auth.foo.com/', 'foo=bar'
16
+ jar.set_cookie 'http://auth.foo.com/', 'auth=135121...;domain=foo.com'
17
+ end
18
+ end
19
+ describe '.get_cookies' do
20
+ it "should let me read back cookies which are set" do
21
+ jar = Jar.new
22
+ jar.set_cookie 'http://foo.com/', 'foo=bar'
23
+ jar.set_cookie 'http://foo.com/', 'bar=baz'
24
+ jar.set_cookie 'http://auth.foo.com/', 'foo=bar'
25
+ jar.set_cookie 'http://auth.foo.com/', 'auth=135121...;domain=foo.com'
26
+ jar.get_cookies('http://foo.com/').should have(3).items
27
+ end
28
+ it "should return cookies longest path first" do
29
+ jar = Jar.new
30
+ uri = 'http://foo.com/a/b/c/d'
31
+ jar.set_cookie uri, 'a=bar'
32
+ jar.set_cookie uri, 'b=baz;path=/a/b/c/d'
33
+ jar.set_cookie uri, 'c=bar;path=/a/b'
34
+ jar.set_cookie uri, 'd=bar;path=/a/'
35
+ cookies = jar.get_cookies(uri)
36
+ cookies.should have(4).items
37
+ cookies[0].name.should == 'b'
38
+ cookies[1].name.should == 'a'
39
+ cookies[2].name.should == 'c'
40
+ cookies[3].name.should == 'd'
41
+ end
42
+ end
43
+ describe '.get_cookie_headers' do
44
+ it "should return cookie headers" do
45
+ jar = Jar.new
46
+ uri = 'http://foo.com/a/b/c/d'
47
+ jar.set_cookie uri, 'a=bar'
48
+ jar.set_cookie uri, 'b=baz;path=/a/b/c/d'
49
+ cookie_headers = jar.get_cookie_headers uri
50
+ cookie_headers.should have(2).items
51
+ cookie_headers.should == [['Cookie', 'b=baz'],['Cookie', 'a=bar']]
52
+ end
53
+ end
54
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dwaite-cookiejar
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - David Waite
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-07 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Allows for parsing and returning cookies in Ruby HTTP client code
17
+ email: david@alkaline-solutions.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/cookiejar/cookie.rb
26
+ - lib/cookiejar/cookie_common.rb
27
+ - lib/cookiejar/cookie_logic.rb
28
+ - lib/cookiejar/jar.rb
29
+ - lib/cookiejar.rb
30
+ - test/cookie_logic_test.rb
31
+ - test/cookie_test.rb
32
+ - test/jar_test.rb
33
+ has_rdoc: false
34
+ homepage: http://alkaline-solutions.com
35
+ post_install_message:
36
+ rdoc_options:
37
+ - --title
38
+ - CookieJar -- Client-side HTTP Cookies
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ requirements: []
54
+
55
+ rubyforge_project:
56
+ rubygems_version: 1.2.0
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: Client-side HTTP Cookie library
60
+ test_files: []
61
+