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 :created_at
attr_accessor :accessed_at attr_accessor :accessed_at
attr_accessor :origin
# :call-seq: # :call-seq:
# new(name, value) # new(name, value)
# new(name, value, attr_hash) # new(name, value, attr_hash)
@ -95,12 +97,21 @@ class HTTP::Cookie
class << self class << self
include URIFix if defined?(URIFix) include URIFix if defined?(URIFix)
# Parses a Set-Cookie header value +set_cookie+ sent from +origin+ # Parses a Set-Cookie header value +set_cookie+ into an array of
# into an array of Cookie objects. Parts (separated by commas) # Cookie objects. Parts (separated by commas) that are malformed
# that are malformed are ignored. # are ignored.
# #
# If a block is given, each cookie object is passed to the block. # 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| [].tap { |cookies|
set_cookie.split(/,(?=[^;,]*=)|,$/).each { |c| set_cookie.split(/,(?=[^;,]*=)|,$/).each { |c|
cookie_elem = c.split(/;+/) cookie_elem = c.split(/;+/)
@ -163,15 +174,21 @@ class HTTP::Cookie
end end
end end
cookie.path ||= (origin + './').path
cookie.secure ||= false cookie.secure ||= false
cookie.domain ||= origin.host
# RFC 6265 4.1.2.2 # RFC 6265 4.1.2.2
cookie.expires = Time.now + cookie.max_age if cookie.max_age cookie.expires = Time.now + cookie.max_age if cookie.max_age
cookie.session = !cookie.expires 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? yield cookie if block_given?
cookies << cookie cookies << cookie
@ -202,6 +219,15 @@ class HTTP::Cookie
@domain = @domain_name.hostname @domain = @domain_name.hostname
end 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) def expires=(t)
@expires = t && (t.is_a?(Time) ? t.httpdate : t.to_s) @expires = t && (t.is_a?(Time) ? t.httpdate : t.to_s)
end end
@ -222,7 +248,7 @@ class HTTP::Cookie
# RFC 6265 5.3 # RFC 6265 5.3
# When the user agent "receives a cookie": # 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) if host.cookie_domain?(@domain_name)
true true
@ -236,7 +262,7 @@ class HTTP::Cookie
def valid_for_uri?(uri) def valid_for_uri?(uri)
return false if secure? && uri.scheme != 'https' 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 end
def to_s def to_s

View file

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