#gaato/mttp_core/cookie

    An RFC 6265 cookie store for HTTP clients, without I/O.

    CookieJar::store reads every Set-Cookie of a response for the URL that was requested; header_for and attach give the Cookie header a request to a URL should carry. Domain and path matching, host-only cookies, Secure, HttpOnly, SameSite, the __Secure- and __Host- prefixes and the storage order follow RFC 6265 and its successor draft, RFC 6265bis.

    The jar reads time from the @rt.Clock it is given, so expiry is deterministic in tests. It is bounded:

    • 50 cookies per domain and 3000 in all by default (max_per_domain, max_total); past a cap, expired cookies go first, then the least recently sent;
    • a cookie whose name and value exceed 4096 bytes, or an attribute past 1024 bytes, is ignored;
    • no cookie lives longer than 400 days.

    Without a public-suffix list, only single-label domains count as public suffixes; pass is_public_suffix to refuse more Domain values. A host written with the root dot (example.com.) is a different host from example.com, as in browsers, and Domain=com. is still a single label.

    ///|
    test "store a response's cookies and send them back" {
    let clock = @sim.FakeClock::new(start_unix_ms=1_000_000L)
    let jar = @cookie.CookieJar::new(clock)
    jar.store(
    @mttp_core.Headers::of([
    ("Set-Cookie", "sid=abc; Path=/; Secure; HttpOnly"),
    ("Set-Cookie", "theme=dark; Max-Age=60"),
    ]),
    "https://example.com/login",
    )
    assert_eq(jar.header_for("https://example.com/"), Some("sid=abc; theme=dark"))
    // Secure cookies stay off plain HTTP.
    assert_eq(jar.header_for("http://example.com/"), Some("theme=dark"))
    clock.advance(61_000)
    assert_eq(jar.header_for("https://example.com/"), Some("sid=abc"))
    }

    pub struct Cookie {
    name : String
    value : String
    domain : String
    host_only : Bool
    path : String
    expires_ms : Int64?
    secure : Bool
    http_only : Bool
    same_site : SameSite?
    creation_ms : Int64
    last_access_ms : Int64
    } derive(Eq,
    Debug
    )

    A stored cookie, with the fields of the RFC 6265bis-22 §5.7 storage model.

    domain is the request host for a host-only cookie and the Domain attribute otherwise. expires_ms is None for a session cookie. Times are Unix milliseconds from the jar's clock; last_access_ms is when the cookie was last stored or sent.

    Cookie::equal

    fn Cookie::equal(Cookie, Cookie) -> Bool

    Compares every field.

    Cookie::is_expired

    fn Cookie::is_expired(self : Cookie, now_ms : Int64) -> Bool

    Whether the cookie has expired at now_ms.

    Cookie::not_equal

    fn Cookie::not_equal(x : Cookie, y : Cookie) -> Bool

    Compares every field.

    Cookie::to_repr

    Debug representation of every field.

    CookieJar

    pub struct CookieJar {
    // private fields
    }

    An RFC 6265bis-22 cookie store for an HTTP client: store takes the Set-Cookie headers of a response, header_for builds the Cookie header of a request.

    It is a client's jar, not a browser's: HttpOnly cookies are sent like any other, and SameSite is kept but not enforced, since a client has no notion of a cross-site request. Without the Public Suffix List it rejects only a Domain of a single label (com, localhost); pass is_public_suffix to reject more. An IP-address host only gets host-only cookies. Hosts must arrive in ASCII form, so a Domain with other characters is ignored.

    A host written with the root dot (example.com.) is a different host from example.com, as in browsers: neither sets nor receives the other's cookies. The public-suffix checks read a Domain without its root dot, so Domain=com. is refused as a single label, and a Domain with an empty label (a..b, com..) is ignored.

    test {
    let jar = @cookie.CookieJar::new(@sim.FakeClock::new())
    jar.store(
    @mttp_core.Headers::of([("Set-Cookie", "sid=1; Path=/; Secure")]),
    "https://example.com/login",
    )
    assert_eq(jar.header_for("https://example.com/"), Some("sid=1"))
    assert_eq(jar.header_for("http://example.com/"), None)
    }

    CookieJar::attach

    Adds this jar's Cookie header for the request URL, unless the caller already supplied one. Returns a copy and leaves the original unchanged.

    CookieJar::clear

    fn CookieJar::clear(self : CookieJar) -> Unit

    Removes every cookie.

    CookieJar::clear_domain

    fn CookieJar::clear_domain(self : CookieJar, domain : String) -> Unit

    Removes the cookies whose domain is domain or one of its subdomains.

    CookieJar::clear_session

    fn CookieJar::clear_session(self : CookieJar) -> Unit

    Removes the session cookies (those without Expires or Max-Age), as RFC 6265bis-22 §5.7 does when the session is over.

    CookieJar::header_for

    fn CookieJar::header_for(self : CookieJar, request_url : String) -> String?

    Returns the Cookie header value for a request to request_url, or None when no cookie matches, and marks the cookies sent as accessed. Longer paths come first, then earlier-created cookies (RFC 6265bis-22 §5.8.3).

    CookieJar::iter

    fn CookieJar::iter(self : CookieJar) -> Iter[Cookie]

    Iterates over a snapshot of the stored cookies, grouped by domain. Expired cookies not yet evicted are included; see remove_expired.

    CookieJar::length

    fn CookieJar::length(self : CookieJar) -> Int

    The number of cookies stored, expired ones not yet evicted included.

    CookieJar::new

    fn CookieJar::new(clock : &
    Clock
    , max_per_domain? : Int, max_total? : Int, is_public_suffix? : (String) -> Bool) -> CookieJar

    Creates an empty jar that reads the time from clock.

    max_per_domain (default 50) and max_total (default 3000) bound how many cookies it keeps; a cookie past either evicts expired cookies first, then the least recently accessed, those that are not Secure first when a domain is over its cap (RFC 6265bis-22 §5.7). Negative limits count as 0. is_public_suffix names further domains a cookie may not be set for, such as a Public Suffix List lookup; single-label domains are always refused. It receives the lowercase Domain without a trailing root dot.

    CookieJar::remove_expired

    fn CookieJar::remove_expired(self : CookieJar) -> Unit

    Removes the cookies that have expired by the clock's current time.

    CookieJar::store

    fn CookieJar::store(self : CookieJar, headers :
    Headers
    , request_url : String) -> Unit

    Stores every Set-Cookie of a response to request_url, in order. Values the RFC 6265 algorithms ignore, and every value when the URL has no host, are skipped.

    SameSite

    pub(all) enum SameSite {
    Strict
    Lax
    NoRestriction
    } derive(Eq,
    Debug
    )

    A cookie's SameSite attribute (RFC 6265bis-22). NoRestriction is SameSite=None.

    SameSite::equal

    fn SameSite::equal(SameSite, SameSite) -> Bool

    Compares attribute values.

    SameSite::not_equal

    fn SameSite::not_equal(x : SameSite, y : SameSite) -> Bool

    Compares attribute values.

    SameSite::to_repr

    Debug representation of the attribute value.