Introduce a new cookie object attribute "origin".

Change the signature of HTTP::Cookie.parse() so that it only
optionally takes an origin URI.  When one is given, the method checks
if each piece of cookie in the header value is valid and acceptable
from the origin to ignore unacceptable cookies.
This commit is contained in:
Akinori MUSHA 2012-10-17 22:13:03 +09:00
parent a1e5e1628a
commit 532101a102
2 changed files with 88 additions and 35 deletions

View file

@ -31,6 +31,8 @@ class HTTP::Cookie
attr_accessor :created_at
attr_accessor :accessed_at
attr_accessor :origin
# :call-seq:
# new(name, value)
# new(name, value, attr_hash)
@ -95,12 +97,21 @@ class HTTP::Cookie
class << self
include URIFix if defined?(URIFix)
# Parses a Set-Cookie header value +set_cookie+ sent from +origin+
# into an array of Cookie objects. Parts (separated by commas)
# that are malformed are ignored.
# Parses a Set-Cookie header value +set_cookie+ into an array of
# Cookie objects. Parts (separated by commas) that are malformed
# are ignored.
#
# If a block is given, each cookie object is passed to the block.
def parse(origin, set_cookie, logger = nil)
#
# The cookie's origin URI/URL and a logger object can be passed in
# +options+ with the keywords +:origin+ and +:logger+,
# respectively.
def parse(set_cookie, options = nil, &block)
if options
logger = options[:logger]
origin = options[:origin] and origin = URI(origin)
end
[].tap { |cookies|
set_cookie.split(/,(?=[^;,]*=)|,$/).each { |c|
cookie_elem = c.split(/;+/)
@ -163,15 +174,21 @@ class HTTP::Cookie
end
end
cookie.path ||= (origin + './').path
cookie.secure ||= false
cookie.domain ||= origin.host
# RFC 6265 4.1.2.2
cookie.expires = Time.now + cookie.max_age if cookie.max_age
cookie.session = !cookie.expires
# Move this in to the cookie jar
if origin
begin
cookie.origin = origin
rescue => e
logger.warn("Invalid cookie for the origin: #{origin} (#{e})") if logger
next
end
end
yield cookie if block_given?
cookies << cookie
@ -202,6 +219,15 @@ class HTTP::Cookie
@domain = @domain_name.hostname
end
def origin=(origin)
origin = URI(origin)
acceptable_from_uri?(origin) or
raise ArgumentError, "unacceptable cookie sent from URI #{origin}"
self.domain ||= origin.host
self.path ||= (origin + './').path
@origin = origin
end
def expires=(t)
@expires = t && (t.is_a?(Time) ? t.httpdate : t.to_s)
end
@ -222,7 +248,7 @@ class HTTP::Cookie
# RFC 6265 5.3
# When the user agent "receives a cookie":
return host.hostname == domain unless @for_domain
return domain.nil? || host.hostname == domain unless @for_domain
if host.cookie_domain?(@domain_name)
true
@ -236,7 +262,7 @@ class HTTP::Cookie
def valid_for_uri?(uri)
return false if secure? && uri.scheme != 'https'
acceptable_from_uri?(uri) && uri.path.start_with?(path)
acceptable_from_uri?(uri) && (@path.nil? || uri.path.start_with?(@path))
end
def to_s

View file

@ -34,7 +34,7 @@ class TestHTTPCookie < Test::Unit::TestCase
dates.each do |date|
cookie = "PREF=1; expires=#{date}"
silently do
HTTP::Cookie.parse(url, cookie) { |c|
HTTP::Cookie.parse(cookie, :origin => url) { |c|
assert c.expires, "Tried parsing: #{date}"
assert_equal(true, c.expires < yesterday)
}
@ -47,7 +47,7 @@ class TestHTTPCookie < Test::Unit::TestCase
uri = URI.parse 'http://example'
HTTP::Cookie.parse uri, cookie_str do |cookie|
HTTP::Cookie.parse cookie_str, :origin => uri do |cookie|
assert_equal 'a', cookie.name
assert_equal 'b', cookie.value
end
@ -58,7 +58,7 @@ class TestHTTPCookie < Test::Unit::TestCase
uri = URI.parse 'http://example'
HTTP::Cookie.parse uri, cookie_str do |cookie|
HTTP::Cookie.parse cookie_str, :origin => uri do |cookie|
assert_equal 'foo', cookie.name
assert_equal 'bar', cookie.value
assert_equal '/', cookie.path
@ -72,7 +72,7 @@ class TestHTTPCookie < Test::Unit::TestCase
uri = URI.parse 'http://example'
HTTP::Cookie.parse uri, cookie_str do |cookie|
HTTP::Cookie.parse cookie_str, :origin => uri do |cookie|
assert_equal 'quoted', cookie.name
assert_equal '"value"', cookie.value
end
@ -81,7 +81,7 @@ class TestHTTPCookie < Test::Unit::TestCase
def test_parse_weird_cookie
cookie = 'n/a, ASPSESSIONIDCSRRQDQR=FBLDGHPBNDJCPCGNCPAENELB; path=/'
url = URI.parse('http://www.searchinnovation.com/')
HTTP::Cookie.parse(url, cookie) { |c|
HTTP::Cookie.parse(cookie, :origin => url) { |c|
assert_equal('ASPSESSIONIDCSRRQDQR', c.name)
assert_equal('FBLDGHPBNDJCPCGNCPAENELB', c.value)
}
@ -90,7 +90,7 @@ class TestHTTPCookie < Test::Unit::TestCase
def test_double_semicolon
double_semi = 'WSIDC=WEST;; domain=.williams-sonoma.com; path=/'
url = URI.parse('http://williams-sonoma.com/')
HTTP::Cookie.parse(url, double_semi) { |cookie|
HTTP::Cookie.parse(double_semi, :origin => url) { |cookie|
assert_equal('WSIDC', cookie.name)
assert_equal('WEST', cookie.value)
}
@ -99,7 +99,7 @@ class TestHTTPCookie < Test::Unit::TestCase
def test_parse_bad_version
bad_cookie = 'PRETANET=TGIAqbFXtt; Name=/PRETANET; Path=/; Version=1.2; Content-type=text/html; Domain=192.168.6.196; expires=Friday, 13-November-2026 23:01:46 GMT;'
url = URI.parse('http://localhost/')
HTTP::Cookie.parse(url, bad_cookie) { |cookie|
HTTP::Cookie.parse(bad_cookie, :origin => url) { |cookie|
assert_nil(cookie.version)
}
end
@ -107,7 +107,7 @@ class TestHTTPCookie < Test::Unit::TestCase
def test_parse_bad_max_age
bad_cookie = 'PRETANET=TGIAqbFXtt; Name=/PRETANET; Path=/; Max-Age=1.2; Content-type=text/html; Domain=192.168.6.196; expires=Friday, 13-November-2026 23:01:46 GMT;'
url = URI.parse('http://localhost/')
HTTP::Cookie.parse(url, bad_cookie) { |cookie|
HTTP::Cookie.parse(bad_cookie, :origin => url) { |cookie|
assert_nil(cookie.max_age)
}
end
@ -122,7 +122,7 @@ class TestHTTPCookie < Test::Unit::TestCase
silently do
dates.each do |date|
cookie = "PREF=1; expires=#{date}"
HTTP::Cookie.parse(url, cookie) { |c|
HTTP::Cookie.parse(cookie, :origin => url) { |c|
assert_equal(true, c.expires.nil?)
}
end
@ -134,7 +134,7 @@ class TestHTTPCookie < Test::Unit::TestCase
cookie_str = 'a=b; domain=.example.com'
cookie = HTTP::Cookie.parse(url, cookie_str).first
cookie = HTTP::Cookie.parse(cookie_str, :origin => url).first
assert_equal 'example.com', cookie.domain
assert cookie.for_domain?
@ -145,7 +145,7 @@ class TestHTTPCookie < Test::Unit::TestCase
cookie_str = 'a=b; domain=example.com'
cookie = HTTP::Cookie.parse(url, cookie_str).first
cookie = HTTP::Cookie.parse(cookie_str, :origin => url).first
assert_equal 'example.com', cookie.domain
assert cookie.for_domain?
@ -156,7 +156,7 @@ class TestHTTPCookie < Test::Unit::TestCase
cookie_str = 'a=b;'
cookie = HTTP::Cookie.parse(url, cookie_str).first
cookie = HTTP::Cookie.parse(cookie_str, :origin => url).first
assert_equal 'example.com', cookie.domain
assert !cookie.for_domain?
@ -167,17 +167,17 @@ class TestHTTPCookie < Test::Unit::TestCase
date = 'Mon, 19 Feb 2012 19:26:04 GMT'
cookie = HTTP::Cookie.parse(url, "name=Akinori; expires=#{date}").first
cookie = HTTP::Cookie.parse("name=Akinori; expires=#{date}", :origin => url).first
assert_equal Time.at(1329679564), cookie.expires
cookie = HTTP::Cookie.parse(url, 'name=Akinori; max-age=3600').first
cookie = HTTP::Cookie.parse('name=Akinori; max-age=3600', :origin => url).first
assert_in_delta Time.now + 3600, cookie.expires, 1
# Max-Age has precedence over Expires
cookie = HTTP::Cookie.parse(url, "name=Akinori; max-age=3600; expires=#{date}").first
cookie = HTTP::Cookie.parse("name=Akinori; max-age=3600; expires=#{date}", :origin => url).first
assert_in_delta Time.now + 3600, cookie.expires, 1
cookie = HTTP::Cookie.parse(url, "name=Akinori; expires=#{date}; max-age=3600").first
cookie = HTTP::Cookie.parse("name=Akinori; expires=#{date}; max-age=3600", :origin => url).first
assert_in_delta Time.now + 3600, cookie.expires, 1
end
@ -191,7 +191,7 @@ class TestHTTPCookie < Test::Unit::TestCase
'name=Akinori; expires=',
'name=Akinori; max-age=',
].each { |str|
cookie = HTTP::Cookie.parse(url, str).first
cookie = HTTP::Cookie.parse(str, :origin => url).first
assert cookie.session, str
}
@ -199,13 +199,13 @@ class TestHTTPCookie < Test::Unit::TestCase
'name=Akinori; expires=Mon, 19 Feb 2012 19:26:04 GMT',
'name=Akinori; max-age=3600',
].each { |str|
cookie = HTTP::Cookie.parse(url, str).first
cookie = HTTP::Cookie.parse(str, :origin => url).first
assert !cookie.session, str
}
end
def test_parse_many
url = URI 'http://example/'
url = URI 'http://localhost/'
cookie_str =
"abc, " \
"name=Aaron; Domain=localhost; Expires=Sun, 06 Nov 2011 00:29:51 GMT; Path=/, " \
@ -221,7 +221,7 @@ class TestHTTPCookie < Test::Unit::TestCase
"no_domain2=no_domain; Expires=Sun, 06 Nov 2011 00:29:53 GMT; no_expires=nope; Domain, " \
"no_domain3=no_domain; Expires=Sun, 06 Nov 2011 00:29:53 GMT; no_expires=nope; Domain="
cookies = HTTP::Cookie.parse url, cookie_str
cookies = HTTP::Cookie.parse cookie_str, :origin => url
assert_equal 13, cookies.length
name = cookies.find { |c| c.name == 'name' }
@ -278,7 +278,7 @@ class TestHTTPCookie < Test::Unit::TestCase
end
end
cookie = nil
HTTP::Cookie.parse(url, cookie_text) { |p_cookie| cookie = p_cookie }
HTTP::Cookie.parse(cookie_text, :origin => url) { |p_cookie| cookie = p_cookie }
assert_equal('12345%7D=ASDFWEE345%3DASda', cookie.to_s)
assert_equal('/', cookie.path)
@ -313,7 +313,7 @@ class TestHTTPCookie < Test::Unit::TestCase
end
end
cookie = nil
HTTP::Cookie.parse(url, cookie_text) { |p_cookie| cookie = p_cookie }
HTTP::Cookie.parse(cookie_text, :origin => url) { |p_cookie| cookie = p_cookie }
assert_equal('12345%7D=', cookie.to_s)
assert_equal('', cookie.value)
@ -351,7 +351,7 @@ class TestHTTPCookie < Test::Unit::TestCase
end
end
cookie = nil
HTTP::Cookie.parse(url, cookie_text) { |p_cookie| cookie = p_cookie }
HTTP::Cookie.parse(cookie_text, :origin => url) { |p_cookie| cookie = p_cookie }
assert_equal('12345%7D=ASDFWEE345%3DASda', cookie.to_s)
assert_equal('/', cookie.path)
@ -388,7 +388,7 @@ class TestHTTPCookie < Test::Unit::TestCase
end
end
cookie = nil
HTTP::Cookie.parse(url, cookie_text) { |p_cookie| cookie = p_cookie }
HTTP::Cookie.parse(cookie_text, :origin => url) { |p_cookie| cookie = p_cookie }
assert_equal('12345%7D=ASDFWEE345%3DASda', cookie.to_s)
assert_equal('/', cookie.path)
@ -424,7 +424,7 @@ class TestHTTPCookie < Test::Unit::TestCase
end
end
cookie = nil
HTTP::Cookie.parse(url, cookie_text) { |p_cookie| cookie = p_cookie }
HTTP::Cookie.parse(cookie_text, :origin => url) { |p_cookie| cookie = p_cookie }
assert_equal('12345%7D=ASDFWEE345%3DASda', cookie.to_s)
assert_equal('/', cookie.path)
@ -469,7 +469,7 @@ class TestHTTPCookie < Test::Unit::TestCase
url = URI.parse('http://host.dom.example.com:8080/')
cookie_str = 'a=b; domain=Example.Com'
cookie = HTTP::Cookie.parse(url, cookie_str).first
cookie = HTTP::Cookie.parse(cookie_str, :origin => url).first
assert 'example.com', cookie.domain
cookie.domain = DomainName(url.host)
@ -485,5 +485,32 @@ class TestHTTPCookie < Test::Unit::TestCase
}
assert 'example.com', cookie.domain
end
def test_origin=
url = URI.parse('http://example.com/path/')
cookie_str = 'a=b'
cookie = HTTP::Cookie.parse(cookie_str).first
cookie.origin = url
assert_equal '/path/', cookie.path
assert_equal 'example.com', cookie.domain
assert_equal false, cookie.for_domain
assert_raises(ArgumentError) {
cookie.origin = URI.parse('http://www.example.com/')
}
cookie_str = 'a=b; domain=.example.com; path=/'
cookie = HTTP::Cookie.parse(cookie_str).first
cookie.origin = url
assert_equal '/', cookie.path
assert_equal 'example.com', cookie.domain
assert_equal true, cookie.for_domain
cookie_str = 'a=b; domain=example.com'
cookie = HTTP::Cookie.parse(cookie_str).first
assert_raises(ArgumentError) {
cookie.origin = URI.parse('http://example.org/')
}
end
end