From 1e4a44a666903424161405922191f4542f196bd8 Mon Sep 17 00:00:00 2001 From: Guillaume Malette Date: Tue, 26 Nov 2024 13:23:20 -0500 Subject: [PATCH 1/2] fix: cookie-av allows arbitrary casing According to [RFC6265](https://httpwg.org/specs/rfc6265.html#sane-set-cookie), cookie attributes are supposed to be ~PascalCase (`Path`, `HttpOnly`, `Secure`, etc). In practice, browsers are lax in their interpretation of cookie attributes and will allow arbitrary casing (`path`, `Path`, `pAtH`, etc). Prior to this PR, `Rack::Test::Cookie` only supported lowercased cookie attributes, but this PR allows it to have any casing, making it behave closer to browsers and other cookie jars. https://github.com/python/cpython/blob/f0d3f10c43c9029378adba11a65b3d1287e4be32/Lib/http/cookiejar.py#L511-L512 https://cs.opensource.google/go/go/+/master:src/net/http/cookie.go;l=126-131;drc=592da0ba474b94b6eceee62b5613f1c9c1ed9c89?q=cookie&ss=go%2Fgo --- lib/rack/test/cookie_jar.rb | 8 +++++--- spec/rack/test/cookie_spec.rb | 22 +++++++++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/lib/rack/test/cookie_jar.rb b/lib/rack/test/cookie_jar.rb index 011f81b..d3ad789 100644 --- a/lib/rack/test/cookie_jar.rb +++ b/lib/rack/test/cookie_jar.rb @@ -28,7 +28,7 @@ def initialize(raw, uri = nil, default_host = DEFAULT_HOST) @raw, options = raw.split(/[;,] */n, 2) @name, @value = parse_query(@raw, ';').to_a.first - @options = parse_query(options, ';') + @options = parse_query(options, ';').map { |k, v| [k.downcase, v] }.to_h if domain = @options['domain'] @exact_domain_match = false @@ -69,7 +69,7 @@ def secure? # Whether the cookie has the httponly flag, indicating it is not available via # a javascript API. def http_only? - @options.key?('HttpOnly') || @options.key?('httponly') + @options.key?('httponly') end # The explicit or implicit path for the cookie. @@ -110,11 +110,13 @@ def <=>(other) # A hash of cookie options, including the cookie value, but excluding the cookie name. def to_h - @options.merge( + hash = @options.merge( 'value' => @value, 'HttpOnly' => http_only?, 'secure' => secure? ) + hash.delete('httponly') + hash end alias to_hash to_h diff --git a/spec/rack/test/cookie_spec.rb b/spec/rack/test/cookie_spec.rb index f88c431..6b0036d 100644 --- a/spec/rack/test/cookie_spec.rb +++ b/spec/rack/test/cookie_spec.rb @@ -49,12 +49,28 @@ def cookie.expired?; true end cookie_string = [ '/', 'csrf_id=ABC123', - 'path=/', - 'expires=Wed, 01 Jan 2020 08:00:00 GMT', + 'path=/cookie', 'HttpOnly' ].join(Rack::Test::CookieJar::DELIMITER) cookie = Rack::Test::Cookie.new(cookie_string) - cookie.path.must_equal '/' + cookie.path.must_equal '/cookie' + end + + it 'attribute names are case-insensitive' do + cookie_string = [ + '/', + 'csrf_id=ABC123', + 'Path=/cookie', + 'Expires=Wed, 01 Jan 2020 08:00:00 GMT', + 'HttpOnly', + 'Secure', + ].join(Rack::Test::CookieJar::DELIMITER) + cookie = Rack::Test::Cookie.new(cookie_string) + + cookie.path.must_equal '/cookie' + cookie.secure?.must_equal true + cookie.http_only?.must_equal true + cookie.expires.must_equal Time.parse('Wed, 01 Jan 2020 08:00:00 GMT') end it 'escapes cookie values' do From 597a5cc6eed758ec925a82a5dd234a8c6981500e Mon Sep 17 00:00:00 2001 From: Guillaume Malette Date: Wed, 27 Nov 2024 13:27:18 -0500 Subject: [PATCH 2/2] fix: Use Hash::[] instead of Array#to_h for Ruby 2.0.0 compatibility --- lib/rack/test/cookie_jar.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rack/test/cookie_jar.rb b/lib/rack/test/cookie_jar.rb index d3ad789..96fa154 100644 --- a/lib/rack/test/cookie_jar.rb +++ b/lib/rack/test/cookie_jar.rb @@ -28,7 +28,7 @@ def initialize(raw, uri = nil, default_host = DEFAULT_HOST) @raw, options = raw.split(/[;,] */n, 2) @name, @value = parse_query(@raw, ';').to_a.first - @options = parse_query(options, ';').map { |k, v| [k.downcase, v] }.to_h + @options = Hash[parse_query(options, ';').map { |k, v| [k.downcase, v] }] if domain = @options['domain'] @exact_domain_match = false