Skip to content
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
7 changes: 5 additions & 2 deletions lib/protocol/http/header/cookie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ def initialize(value = nil)
#
# @returns [Hash(String, HTTP::Cookie)] a hash where keys are cookie names and values are {HTTP::Cookie} objects.
def to_h
cookies = self.collect do |string|
HTTP::Cookie.parse(string)
cookies = self.flat_map do |string|
# Each header field can contain multiple cookie pairs separated by semicolons:
string.split(/\s*;\s*/).map do |pair|
HTTP::Cookie.parse(pair)
end
end

cookies.map{|cookie| [cookie.name, cookie]}.to_h
Expand Down
69 changes: 48 additions & 21 deletions test/protocol/http/header/cookie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# Copyright, 2022, by Herrick Fang.

require "protocol/http/header/cookie"
require "protocol/http/headers"

describe Protocol::HTTP::Header::Cookie do
let(:header) {subject.parse(description)}
Expand All @@ -16,40 +17,46 @@
expect(header).to be == ["session=abc123"]
end

with "session=123; secure" do
with "session=123; user_id=42" do
it "can parse cookies" do
expect(cookies).to have_keys("session")
expect(cookies).to have_keys("session", "user_id")

session = cookies["session"]
expect(session).to have_attributes(
name: be == "session",
value: be == "123",
directives: be == {},
)

user_id = cookies["user_id"]
expect(user_id).to have_attributes(
name: be == "user_id",
value: be == "42",
directives: be == {},
)
expect(session.directives).to have_keys("secure")
end
end

with "session=123; path=/; secure" do
it "can parse cookies" do
session = cookies["session"]
expect(session).to have_attributes(
name: be == "session",
value: be == "123",
directives: be == {"path" => "/", "secure" => true},
)
with "empty=; token=abc==" do
it "preserves empty values and equals signs" do
expect(cookies["empty"].value).to be == ""
expect(cookies["token"].value).to be == "abc=="
end
end

with "session=abc123; secure" do
it "can parse cookies" do
expect(cookies).to have_keys("session")

session = cookies["session"]
expect(session).to have_attributes(
name: be == "session",
value: be == "abc123",
)
expect(session.directives).to have_keys("secure")
with "first=1 ; second=2;\tthird=3" do
it "ignores whitespace around separators" do
expect(cookies.transform_values(&:value)).to be == {
"first" => "1",
"second" => "2",
"third" => "3",
}
end
end

with "session=first; session=second" do
it "uses the last value for duplicate names" do
expect(cookies["session"].value).to be == "second"
end
end

Expand All @@ -65,5 +72,25 @@
it "joins cookies with semicolons and spaces per RFC 6265" do
expect(header.to_s).to be == "session=abc123; user_id=42; token=xyz789"
end

it "parses cookies from multiple header fields" do
expect(cookies).to have_keys("session", "user_id", "token")
end
end

it "parses cookies through protocol headers" do
headers = Protocol::HTTP::Headers[[
["cookie", "session=abc123; user_id=42"],
["cookie", "token=xyz789"],
]]

header = headers["cookie"]

expect(header).to be_a(subject)
expect(header.to_h.transform_values(&:value)).to be == {
"session" => "abc123",
"user_id" => "42",
"token" => "xyz789",
}
end
end
44 changes: 43 additions & 1 deletion test/protocol/http/header/set_cookie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# Copyright, 2026, by Samuel Williams.

require "protocol/http/header/set_cookie"
require "protocol/http/headers"

describe Protocol::HTTP::Header::SetCookie do
let(:header) do
Expand All @@ -19,6 +20,47 @@
end

it "can extract parsed cookies" do
expect(header.to_h).to have_keys("session", "theme")
cookies = header.to_h

expect(cookies).to have_keys("session", "theme")
expect(cookies["session"]).to have_attributes(
value: be == "abc123",
directives: be == {"Path" => "/"},
)
expect(cookies["theme"]).to have_attributes(
value: be == "dark",
directives: be == {"HttpOnly" => true},
)
end

it "preserves cookie attributes" do
header = subject.parse("session=abc123; Path=/; HttpOnly; SameSite=Lax; Max-Age=3600; Expires=Wed, 21 Oct 2015 07:28:00 GMT")
cookie = header.to_h["session"]

expect(cookie.directives).to be == {
"Path" => "/",
"HttpOnly" => true,
"SameSite" => "Lax",
"Max-Age" => "3600",
"Expires" => "Wed, 21 Oct 2015 07:28:00 GMT",
}
end

it "preserves separate fields through protocol headers" do
headers = Protocol::HTTP::Headers[[
["set-cookie", "session=abc123; Path=/; HttpOnly"],
["set-cookie", "theme=dark; SameSite=Lax"],
]]

header = headers["set-cookie"]
cookies = header.to_h

expect(header).to be_a(subject)
expect(header).to be == [
"session=abc123; Path=/; HttpOnly",
"theme=dark; SameSite=Lax",
]
expect(cookies["session"].directives).to be == {"Path" => "/", "HttpOnly" => true}
expect(cookies["theme"].directives).to be == {"SameSite" => "Lax"}
end
end
Loading