Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stringification for HTTP::Cookie #15240

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions spec/std/http/cookie_spec.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "spec"
require "http/cookie"
require "http/headers"
require "spec/helpers/string"

private def parse_first_cookie(header)
cookies = HTTP::Cookie::Parser.parse_cookies(header)
Expand Down Expand Up @@ -145,24 +146,38 @@ module HTTP
end

describe "#to_set_cookie_header" do
it { HTTP::Cookie.new("x", "v$1").to_set_cookie_header.should eq "x=v$1" }
it { assert_prints HTTP::Cookie.new("x", "v$1").to_set_cookie_header, "x=v$1" }

it { HTTP::Cookie.new("x", "seven", domain: "127.0.0.1").to_set_cookie_header.should eq "x=seven; domain=127.0.0.1" }
it { assert_prints HTTP::Cookie.new("x", "seven", domain: "127.0.0.1").to_set_cookie_header, "x=seven; domain=127.0.0.1" }

it { HTTP::Cookie.new("x", "y", path: "/").to_set_cookie_header.should eq "x=y; path=/" }
it { HTTP::Cookie.new("x", "y", path: "/example").to_set_cookie_header.should eq "x=y; path=/example" }
it { assert_prints HTTP::Cookie.new("x", "y", path: "/").to_set_cookie_header, "x=y; path=/" }
it { assert_prints HTTP::Cookie.new("x", "y", path: "/example").to_set_cookie_header, "x=y; path=/example" }

it { HTTP::Cookie.new("x", "expiring", expires: Time.unix(1257894000)).to_set_cookie_header.should eq "x=expiring; expires=Tue, 10 Nov 2009 23:00:00 GMT" }
it { HTTP::Cookie.new("x", "expiring-1601", expires: Time.utc(1601, 1, 1, 1, 1, 1, nanosecond: 1)).to_set_cookie_header.should eq "x=expiring-1601; expires=Mon, 01 Jan 1601 01:01:01 GMT" }
it { assert_prints HTTP::Cookie.new("x", "expiring", expires: Time.unix(1257894000)).to_set_cookie_header, "x=expiring; expires=Tue, 10 Nov 2009 23:00:00 GMT" }
it { assert_prints HTTP::Cookie.new("x", "expiring-1601", expires: Time.utc(1601, 1, 1, 1, 1, 1, nanosecond: 1)).to_set_cookie_header, "x=expiring-1601; expires=Mon, 01 Jan 1601 01:01:01 GMT" }

it "samesite" do
HTTP::Cookie.new("x", "samesite-default", samesite: nil).to_set_cookie_header.should eq "x=samesite-default"
HTTP::Cookie.new("x", "samesite-lax", samesite: :lax).to_set_cookie_header.should eq "x=samesite-lax; SameSite=Lax"
HTTP::Cookie.new("x", "samesite-strict", samesite: :strict).to_set_cookie_header.should eq "x=samesite-strict; SameSite=Strict"
HTTP::Cookie.new("x", "samesite-none", samesite: :none).to_set_cookie_header.should eq "x=samesite-none; SameSite=None"
assert_prints HTTP::Cookie.new("x", "samesite-default", samesite: nil).to_set_cookie_header, "x=samesite-default"
assert_prints HTTP::Cookie.new("x", "samesite-lax", samesite: :lax).to_set_cookie_header, "x=samesite-lax; SameSite=Lax"
assert_prints HTTP::Cookie.new("x", "samesite-strict", samesite: :strict).to_set_cookie_header, "x=samesite-strict; SameSite=Strict"
assert_prints HTTP::Cookie.new("x", "samesite-none", samesite: :none).to_set_cookie_header, "x=samesite-none; SameSite=None"
end

it { HTTP::Cookie.new("empty-value", "").to_set_cookie_header.should eq "empty-value=" }
it { assert_prints HTTP::Cookie.new("empty-value", "").to_set_cookie_header, "empty-value=" }
end

describe "#to_s" do
it "stringifies" do
HTTP::Cookie.new("foo", "bar").to_s.should eq "foo=bar"
HTTP::Cookie.new("x", "y", domain: "example.com", path: "/foo", expires: Time.unix(1257894000), samesite: :lax).to_s.should eq "x=y; domain=example.com; path=/foo; expires=Tue, 10 Nov 2009 23:00:00 GMT; SameSite=Lax"
end
end

describe "#inspect" do
it "stringifies" do
HTTP::Cookie.new("foo", "bar").inspect.should eq %(HTTP::Cookie["foo=bar"])
HTTP::Cookie.new("x", "y", domain: "example.com", path: "/foo", expires: Time.unix(1257894000), samesite: :lax).inspect.should eq %(HTTP::Cookie["x=y; domain=example.com; path=/foo; expires=Tue, 10 Nov 2009 23:00:00 GMT; SameSite=Lax"])
end
end

describe "#valid? & #validate!" do
Expand Down
75 changes: 63 additions & 12 deletions src/http/cookie.cr
Original file line number Diff line number Diff line change
Expand Up @@ -104,31 +104,82 @@ module HTTP
end
end

# Returns an unambiguous string representation of this cookie.
#
# It uses the `Set-Cookie` serialization from `#to_set_cookie_header` which
# represents the full state of the cookie.
#
# ```
# HTTP::Cookie.new("foo", "bar").inspect # => HTTP::Cookie["foo=bar"]
# HTTP::Cookie.new("foo", "bar", domain: "example.com").inspect # => HTTP::Cookie["foo=bar; domain=example.com"]
# ```
def inspect(io : IO) : Nil
io << "HTTP::Cookie["
to_s.inspect(io)
io << "]"
end

# Returns a string representation of this cookie.
#
# It uses the `Set-Cookie` serialization from `#to_set_cookie_header` which
# represents the full state of the cookie.
#
# ```
# HTTP::Cookie.new("foo", "bar").to_s # => "foo=bar"
# HTTP::Cookie.new("foo", "bar", domain: "example.com").to_s # => "foo=bar; domain=example.com"
# ```
def to_s(io : IO) : Nil
to_set_cookie_header(io)
end

# Returns a string representation of this cookie in the format used by the
# `Set-Cookie` header of an HTTP response.
#
# ```
# HTTP::Cookie.new("foo", "bar").to_set_cookie_header # => "foo=bar"
# HTTP::Cookie.new("foo", "bar", domain: "example.com").to_set_cookie_header # => "foo=bar; domain=example.com"
# ```
def to_set_cookie_header : String
String.build do |header|
to_set_cookie_header(header)
end
end

# :ditto:
def to_set_cookie_header(io : IO) : Nil
path = @path
expires = @expires
max_age = @max_age
domain = @domain
samesite = @samesite
String.build do |header|
to_cookie_header(header)
header << "; domain=#{domain}" if domain
header << "; path=#{path}" if path
header << "; expires=#{HTTP.format_time(expires)}" if expires
header << "; max-age=#{max_age.to_i}" if max_age
header << "; Secure" if @secure
header << "; HttpOnly" if @http_only
header << "; SameSite=#{samesite}" if samesite
header << "; #{@extension}" if @extension
end
end

to_cookie_header(io)
io << "; domain=#{domain}" if domain
io << "; path=#{path}" if path
io << "; expires=#{HTTP.format_time(expires)}" if expires
io << "; max-age=#{max_age.to_i}" if max_age
io << "; Secure" if @secure
io << "; HttpOnly" if @http_only
io << "; SameSite=#{samesite}" if samesite
io << "; #{@extension}" if @extension
end

# Returns a string representation of this cookie in the format used by the
# `Cookie` header of an HTTP request.
# This includes only the `#name` and `#value`. All other attributes are left
# out.
#
# ```
# HTTP::Cookie.new("foo", "bar").to_cookie_header # => "foo=bar"
# HTTP::Cookie.new("foo", "bar", domain: "example.com").to_cookie_header # => "foo=bar
# ```
def to_cookie_header : String
String.build(@name.bytesize + @value.bytesize + 1) do |io|
to_cookie_header(io)
end
end

# :ditto:
def to_cookie_header(io) : Nil
io << @name
io << '='
Expand Down
Loading