diff --git a/spec/std/http/cookie_spec.cr b/spec/std/http/cookie_spec.cr index 218bfd9c608e..65a34a355d02 100644 --- a/spec/std/http/cookie_spec.cr +++ b/spec/std/http/cookie_spec.cr @@ -132,8 +132,8 @@ module HTTP it "raises on invalid value" do cookie = HTTP::Cookie.new("x", "") invalid_values = { - '"', ',', ';', '\\', # invalid printable ascii characters - ' ', '\r', '\t', '\n', # non-printable ascii characters + '"', ',', ';', '\\', # invalid printable ascii characters + '\r', '\t', '\n', # non-printable ascii characters }.map { |c| "foo#{c}bar" } invalid_values.each do |invalid_value| @@ -235,12 +235,6 @@ module HTTP cookie.to_set_cookie_header.should eq("key=value") end - it "parse_set_cookie with space" do - cookie = parse_set_cookie("key=value; path=/test") - parse_set_cookie("key=value;path=/test").should eq cookie - parse_set_cookie("key=value; \t\npath=/test").should eq cookie - end - it "parses key=" do cookie = parse_first_cookie("key=") cookie.name.should eq("key") @@ -285,9 +279,25 @@ module HTTP first.value.should eq("bar") second.value.should eq("baz") end + + it "parses cookie with spaces in value" do + parse_first_cookie(%[key=some value]).value.should eq "some value" + parse_first_cookie(%[key="some value"]).value.should eq "some value" + end end describe "parse_set_cookie" do + it "with space" do + cookie = parse_set_cookie("key=value; path=/test") + parse_set_cookie("key=value;path=/test").should eq cookie + parse_set_cookie("key=value; \t\npath=/test").should eq cookie + end + + it "parses cookie with spaces in value" do + parse_set_cookie(%[key=some value]).value.should eq "some value" + parse_set_cookie(%[key="some value"]).value.should eq "some value" + end + it "parses path" do cookie = parse_set_cookie("key=value; path=/test") cookie.name.should eq("key") diff --git a/src/http/cookie.cr b/src/http/cookie.cr index 83b41297707e..e17c5b3c9e29 100644 --- a/src/http/cookie.cr +++ b/src/http/cookie.cr @@ -98,7 +98,7 @@ module HTTP value.each_byte do |byte| # valid characters for cookie-value per https://tools.ietf.org/html/rfc6265#section-4.1.1 # all printable ASCII characters except ' ', ',', '"', ';' and '\\' - if !byte.in?(0x21...0x7f) || byte.in?(0x22, 0x2c, 0x3b, 0x5c) + if !byte.in?(0x20...0x7f) || byte.in?(0x22, 0x2c, 0x3b, 0x5c) raise IO::Error.new("Invalid cookie value") end end @@ -196,7 +196,7 @@ module HTTP module Parser module Regex CookieName = /[^()<>@,;:\\"\/\[\]?={} \t\x00-\x1f\x7f]+/ - CookieOctet = /[!#-+\--:<-\[\]-~]/ + CookieOctet = /[!#-+\--:<-\[\]-~ ]/ CookieValue = /(?:"#{CookieOctet}*"|#{CookieOctet}*)/ CookiePair = /(?#{CookieName})=(?#{CookieValue})/ DomainLabel = /[A-Za-z0-9\-]+/ @@ -230,7 +230,7 @@ module HTTP def parse_cookies(header, &) header.scan(CookieString).each do |pair| value = pair["value"] - if value.starts_with?('"') + if value.starts_with?('"') && value.ends_with?('"') # Unwrap quoted cookie value value = value.byte_slice(1, value.bytesize - 2) end @@ -251,8 +251,14 @@ module HTTP expires = parse_time(match["expires"]?) max_age = match["max_age"]?.try(&.to_i64.seconds) + # Unwrap quoted cookie value + cookie_value = match["value"] + if cookie_value.starts_with?('"') && cookie_value.ends_with?('"') + cookie_value = cookie_value.byte_slice(1, cookie_value.bytesize - 2) + end + Cookie.new( - match["name"], match["value"], + match["name"], cookie_value, path: match["path"]?, expires: expires, domain: match["domain"]?,