http-cookie_jar v0.2.0

Standalone HTTP cookie jar for Crystal stdlib HTTP types

HTTP::CookieJar

Crystal CI GitHub release License

Standalone HTTP cookie jar for Crystal stdlib HTTP types.

HTTP::CookieJar builds on top of:

  • HTTP::Cookie
  • HTTP::Cookies
  • HTTP::Headers
  • URI

Status

This shard is usable today, but still early-stage.

It already covers the main workflow needed by real HTTP clients:

  • extract cookies from responses
  • store them in memory
  • return matching cookies for later requests
  • generate request Cookie headers

It does not yet aim for full feature parity with Python's http.cookiejar or complete RFC 6265 edge-case coverage.

Current functionality:

  • in-memory cookie storage
  • add single cookies or HTTP::Cookies
  • extract cookies from Set-Cookie response headers
  • return cookies for a request URI
  • add the Cookie header to request headers
  • RFC-oriented domain matching
  • RFC-oriented path matching
  • host-only and domain cookie distinction
  • replacement by cookie name/domain/path
  • ordered cookie return by path specificity
  • clear all cookies
  • clear cookies by domain/path/name
  • clear expired cookies
  • Max-Age aware expiration handling
  • immediate removal of expired replacement cookies
  • JSON persistence
  • file-backed cookie jars
  • Mozilla / Netscape cookies.txt persistence
  • domain normalization
  • RFC-style cookie path normalization and defaulting
  • host validation during acceptance and return
  • configurable cookie policy

Installation

Add this to your application's shard.yml:

dependencies:
  cookie_jar:
    github: mamantoha/http-cookie_jar

Then require it:

require "cookie_jar"

Usage

Create a jar

require "cookie_jar"

jar = HTTP::CookieJar.new

Add cookies

jar.add(
  "https://example.com/login",
  HTTP::Cookie.new("session_id", "abc123")
)

jar.add(
  "https://example.com/login",
  HTTP::Cookie.new("theme", "dark", path: "/", secure: true)
)

Add multiple cookies

cookies = HTTP::Cookies.new
cookies << HTTP::Cookie.new("session_id", "abc123")
cookies << HTTP::Cookie.new("theme", "dark")

jar.add("https://example.com/login", cookies)

Extract cookies from response headers

headers = HTTP::Headers{
  "Set-Cookie" => [
    "session_id=abc123; Path=/; HttpOnly",
    "theme=dark; Path=/",
  ],
}

jar.extract(headers, "https://example.com/login")

Return cookies for a request URI

cookies = jar.cookies_for("https://example.com/account")

cookies["session_id"].value
# => "abc123"

Domain and path matching

jar.add(
  "https://example.com/login",
  HTTP::Cookie.new("session_id", "abc123", domain: "example.com", path: "/account")
)

jar.cookies_for("https://api.example.com/account/settings")["session_id"].value
# => "abc123"

jar.cookies_for("https://api.example.com/admin")["session_id"]?
# => nil

Cookies are returned in a stable order, with more specific paths first.

Add the Cookie header to request headers

headers = HTTP::Headers.new

jar.add_cookie_header(headers, "https://example.com/account")

headers["Cookie"]?
# => "session_id=abc123; theme=dark"

Use with HTTP::Client

client = HTTP::Client.new(URI.parse("https://example.com"))
jar = HTTP::CookieJar.new

login_headers = HTTP::Headers.new
jar.add_cookie_header(login_headers, "https://example.com/login")

login_response = client.post("/login", headers: login_headers, body: "user=john&password=secret")
jar.extract(login_response.headers, "https://example.com/login")

account_headers = HTTP::Headers.new
jar.add_cookie_header(account_headers, "https://example.com/account")

account_response = client.get("/account", headers: account_headers)

Use with HTTP::Client request hooks

client = HTTP::Client.new(URI.parse("https://example.com"))
jar = HTTP::CookieJar.new

client.before_request do |request|
  uri = URI.parse("https://example.com#{request.resource}")
  jar.add_cookie_header(request.headers, uri)
end

response = client.get("/login")
jar.extract(response.headers, "https://example.com/login")

This pattern lets the jar stay independent while still fitting naturally into a stateful client workflow.

Clear cookies

jar.clear

jar.clear("example.com")
jar.clear("example.com", "/")
jar.clear("example.com", "/", "session_id")

jar.clear_expired

Replacement and deletion semantics

jar.add("https://example.com", HTTP::Cookie.new("session_id", "old", path: "/"))
jar.add("https://example.com", HTTP::Cookie.new("session_id", "new", path: "/"))

jar.cookies_for("https://example.com/account")["session_id"].value
# => "new"

jar.add(
  "https://example.com",
  HTTP::Cookie.new("session_id", "", path: "/", max_age: Time::Span.zero)
)

jar.cookies_for("https://example.com/account")["session_id"]?
# => nil

Persist the jar to disk

jar = HTTP::MozillaCookieJar.new("cookies.txt")

jar.extract(login_response.headers, "https://example.com/login")
jar.save(include_session: true)

restored = HTTP::MozillaCookieJar.new("cookies.txt")
restored.load
restored.cookies_for("https://example.com/account")

Use include_session: true when you want to persist session cookies for later reuse.

JSON persistence

File.write("cookiejar.json", jar.to_json)

restored = HTTP::CookieJar.from_json(File.read("cookiejar.json"))
restored.cookies_for("https://example.com/account")

Use a Mozilla / Netscape cookie jar

jar = HTTP::MozillaCookieJar.new("cookies.txt")

jar.add(
  "https://example.com/login",
  HTTP::Cookie.new("session_id", "abc123", path: "/", expires: Time.utc + 1.day)
)

jar.save
jar.load

This format is compatible with the classic Netscape cookies.txt layout used by tools such as curl.

Policy

HTTP::CookieJar accepts a policy object.

The default policy is HTTP::DefaultCookiePolicy.

policy = HTTP::DefaultCookiePolicy.new(
  allowed_domains: ["example.com"],
  blocked_domains: ["tracker.example"],
)

jar = HTTP::CookieJar.new(policy: policy)

Current policy behavior:

  • optional domain allow-list
  • optional domain block-list
  • host-only cookies only return to the original host
  • domain cookies may return to matching subdomains
  • domain cookies are rejected for IP hosts
  • low-quality domain attributes such as com are rejected
  • optional country-code second-level domain rejection (strict_domain)
  • path matching is enforced for returned cookies
  • secure cookies are only returned for secure schemes
  • empty request hosts are rejected

Strict domain policy

Like Python's http.cookiejar.DefaultCookiePolicy, the default policy offers an optional strict_domain heuristic, disabled by default:

policy = HTTP::DefaultCookiePolicy.new(strict_domain: true)
jar = HTTP::CookieJar.new(policy: policy)

When enabled, it rejects two-label Domain attributes with a two-character final label and a common second-level label (for example, co.uk, gov.uk, or co.nz). Case and leading dots are normalized. These Domain attributes are also rejected when they equal the response host; host-only cookies remain supported. Valid parent-domain cookies such as example.co.uk remain supported.

This is a limited heuristic, not Public Suffix List validation. It does not cover private suffixes such as github.io, wildcard rules, or exception rules, and may reject registrable domains that fit the pattern. The default remains permissive, so the attacker.co.uk to victim.co.uk injection remains possible unless strict mode is enabled. Like Python's option, this affects acceptance only; it does not revalidate cookies loaded from storage. No dependency, bundled list, Python installation, or network access is required.

API

Main types:

  • HTTP::CookieJar
  • HTTP::CookieJar::Entry
  • HTTP::CookiePolicy
  • HTTP::DefaultCookiePolicy
  • HTTP::FileCookieJar
  • HTTP::MozillaCookieJar

Main methods:

  • HTTP::CookieJar#add
  • HTTP::CookieJar#extract
  • HTTP::CookieJar#cookies_for
  • HTTP::CookieJar#add_cookie_header
  • HTTP::CookieJar#clear
  • HTTP::CookieJar#clear_expired
  • HTTP::CookieJar#to_json
  • HTTP::CookieJar.from_json
  • HTTP::FileCookieJar#save
  • HTTP::FileCookieJar#load

Samples

Development

Run the specs from the shard root:

crystal spec

Contributors

  • mamantoha Anton Maminov - creator, maintainer

License

Copyright: 2026 Anton Maminov (anton.maminov@gmail.com)

This library is distributed under the MIT license. Please see the LICENSE file.

Repository

http-cookie_jar

Owner
Statistic
  • 0
  • 0
  • 0
  • 1
  • 0
  • 5 days ago
  • March 26, 2026
License

MIT License

Links
Synced at

Wed, 21 Oct 2026 15:58:45 GMT

Languages