Skip to content

Commit

Permalink
Add stringification for HTTP::Cookie (#15240)
Browse files Browse the repository at this point in the history
* Add `Cookie#to_set_cookie_header(IO)`
* Add docs for `Cookie#to_cookie_header`
* Add `Cookie#to_s`
* Add `Cookie#inspect`
  • Loading branch information
straight-shoota authored Dec 3, 2024
1 parent d6d41b7 commit aca8dd4
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 23 deletions.
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

0 comments on commit aca8dd4

Please sign in to comment.