dwaite-cookiejar 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+