From a238d18a5d0711833f2fa7f72e61bd379aeab62b Mon Sep 17 00:00:00 2001 From: Jerome Lacoste Date: Sat, 2 Apr 2016 23:49:22 +0200 Subject: [PATCH 1/3] Preserve quotes when parsing server cookie #11 We keep track of the original quoted value when it is already quoted, and we avoid consider values to be quoted if they start with a quote. --- lib/http/cookie.rb | 10 +++++++++- lib/http/cookie/scanner.rb | 12 ++++++++---- test/test_http_cookie.rb | 12 +++++++++++- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/lib/http/cookie.rb b/lib/http/cookie.rb index 3df776a..cd55719 100644 --- a/lib/http/cookie.rb +++ b/lib/http/cookie.rb @@ -377,6 +377,13 @@ def value= value # RFC 6265 4.1.1 # cookie-name may not match: # /[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/ + orig = value + if m = value.match(/^"(.*)"$/) + @raw_value = value + value = m[1] + else + @raw_value = nil + end @value = value end @@ -594,7 +601,8 @@ def valid_for_uri?(uri) # Returns a string for use in the Cookie header, i.e. `name=value` # or `name="value"`. def cookie_value - "#{@name}=#{Scanner.quote(@value)}" + v = ( @raw_value.nil? ? Scanner.quote(@value) : @raw_value ) + "#{@name}=#{v}" end alias to_s cookie_value diff --git a/lib/http/cookie/scanner.rb b/lib/http/cookie/scanner.rb index cb0d4e5..15a8bc3 100644 --- a/lib/http/cookie/scanner.rb +++ b/lib/http/cookie/scanner.rb @@ -55,10 +55,14 @@ def scan_value case when scan(/[^,;"]+/) s << matched - when skip(/"/) - # RFC 6265 2.2 - # A cookie-value may be DQUOTE'd. - s << scan_dquoted + when scan(/"/) + if s.length == 0 + # RFC 6265 2.2 + # A cookie-value may be DQUOTE'd. + s << '"' << scan_dquoted << '"' + else + s << matched + end when check(/;|#{RE_COOKIE_COMMA}/o) break else diff --git a/test/test_http_cookie.rb b/test/test_http_cookie.rb index c666f97..8cf47ba 100644 --- a/test/test_http_cookie.rb +++ b/test/test_http_cookie.rb @@ -111,6 +111,7 @@ def test_parse_quoted assert_equal 1, HTTP::Cookie.parse(cookie_str, uri) { |cookie| assert_equal 'quoted', cookie.name assert_equal 'value', cookie.value + assert_equal 'quoted="value"', cookie.cookie_value }.size end @@ -430,7 +431,10 @@ def test_cookie_with_secure def test_cookie_value [ ['foo="bar baz"', 'bar baz'], + ['foo="bar baz"', '"bar baz"'], ['foo="bar\"; \"baz"', 'bar"; "baz'], + ['foo="bar\"; \"baz"', '"bar\"; \"baz"'], + ['foo="ba\"r baz"', '"ba\"r baz"'], ].each { |cookie_value, value| cookie = HTTP::Cookie.new('foo', value) assert_equal(cookie_value, cookie.cookie_value) @@ -453,8 +457,14 @@ def test_cookie_value assert_equal 3, hash.size + parsed_pairs = [ + ['Foo', 'value1'], + ['Bar', '"value 2"'], + ['Baz', 'value3'], + ] + hash.each_pair { |name, value| - _, pvalue = pairs.assoc(name) + _, pvalue = parsed_pairs.assoc(name) assert_equal pvalue, value } end From cf9e771e8ccaad17bbe2782572e7de4830165a80 Mon Sep 17 00:00:00 2001 From: Jerome Lacoste Date: Sun, 3 Apr 2016 10:24:45 +0200 Subject: [PATCH 2/3] Make YAML serialization/deserialization able to keep track of the raw_value (and original quoting) --- lib/http/cookie.rb | 4 +++- test/test_http_cookie.rb | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/http/cookie.rb b/lib/http/cookie.rb index cd55719..3159ea2 100644 --- a/lib/http/cookie.rb +++ b/lib/http/cookie.rb @@ -25,7 +25,7 @@ class HTTP::Cookie UNIX_EPOCH = Time.at(0) PERSISTENT_PROPERTIES = %w[ - name value + name value raw_value domain for_domain path secure httponly expires max_age @@ -387,6 +387,8 @@ def value= value @value = value end + attr_accessor :raw_value + attr_reader :domain # See #domain. diff --git a/test/test_http_cookie.rb b/test/test_http_cookie.rb index 8cf47ba..ff806d7 100644 --- a/test/test_http_cookie.rb +++ b/test/test_http_cookie.rb @@ -84,6 +84,7 @@ def test_parse_no_space assert_equal 1, HTTP::Cookie.parse(cookie_str, uri) { |cookie| assert_equal 'foo', cookie.name assert_equal 'bar', cookie.value + assert_equal nil, cookie.raw_value assert_equal '/', cookie.path assert_equal Time.at(1320539286), cookie.expires }.size @@ -111,6 +112,7 @@ def test_parse_quoted assert_equal 1, HTTP::Cookie.parse(cookie_str, uri) { |cookie| assert_equal 'quoted', cookie.name assert_equal 'value', cookie.value + assert_equal '"value"', cookie.raw_value assert_equal 'quoted="value"', cookie.cookie_value }.size end @@ -1072,6 +1074,24 @@ def test_valid_for_uri? } end + def test_yaml_quotes + require 'yaml' + uri = URI.parse('http://localhost/') + cookie_str = 'foo="bar"; Path=/' + assert_equal 1, HTTP::Cookie.parse(cookie_str, uri) { |cookie| + assert_equal 'foo', cookie.name + assert_equal 'bar', cookie.value + assert_equal '"bar"', cookie.raw_value + + ycookie = YAML.load(cookie.to_yaml) + puts ycookie + assert_equal 'bar', ycookie.value + assert_equal '"bar"', ycookie.raw_value + assert_equal cookie_str, ycookie.set_cookie_value + + }.size + end + def test_yaml_expires require 'yaml' cookie = HTTP::Cookie.new(cookie_values) From a8a7ff316972053f408cd940442307b8505e6c84 Mon Sep 17 00:00:00 2001 From: Jerome Lacoste Date: Sun, 3 Apr 2016 11:32:53 +0200 Subject: [PATCH 3/3] Different strategy to initialize raw_value at parsing time, and not at field initialization time, which could cause unexepected results on ruby 1.8.7 --- lib/http/cookie.rb | 15 ++++++++++----- test/test_http_cookie.rb | 1 - 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/http/cookie.rb b/lib/http/cookie.rb index 3159ea2..2fbab7d 100644 --- a/lib/http/cookie.rb +++ b/lib/http/cookie.rb @@ -139,6 +139,7 @@ def initialize(*args) args.pop else self.name, self.value = args # value is set to nil + adjust_raw_value return end when 2..3 @@ -149,6 +150,7 @@ def initialize(*args) argc == 2 or raise ArgumentError, "wrong number of arguments (#{argc} for 1-3)" self.name, self.value = args + adjust_raw_value return end else @@ -204,6 +206,7 @@ def initialize(*args) self.origin = origin if origin self.max_age = max_age if max_age self.value = value.nil? && (@expires || @max_age) ? '' : value + adjust_raw_value end autoload :Scanner, 'http/cookie/scanner' @@ -377,14 +380,16 @@ def value= value # RFC 6265 4.1.1 # cookie-name may not match: # /[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/ - orig = value - if m = value.match(/^"(.*)"$/) - @raw_value = value - value = m[1] + @value = value + end + + def adjust_raw_value + if m = @value.match(/^"(.*)"$/) + @raw_value = @value + @value = m[1] else @raw_value = nil end - @value = value end attr_accessor :raw_value diff --git a/test/test_http_cookie.rb b/test/test_http_cookie.rb index ff806d7..6e2af09 100644 --- a/test/test_http_cookie.rb +++ b/test/test_http_cookie.rb @@ -1084,7 +1084,6 @@ def test_yaml_quotes assert_equal '"bar"', cookie.raw_value ycookie = YAML.load(cookie.to_yaml) - puts ycookie assert_equal 'bar', ycookie.value assert_equal '"bar"', ycookie.raw_value assert_equal cookie_str, ycookie.set_cookie_value