Cookie
One small class to read, write and delete HTTP cookies, with the sharp edges filed off.
Bake a cookie and watch the header →
The three operations
use Cookie;
# READ — parse the incoming Cookie: header (defaults to $ENV{HTTP_COOKIE})
my %jar = Cookie->read;
my $sid = Cookie->get('session');
# WRITE — build and emit a Set-Cookie: header
my $c = Cookie->new(
name => 'session',
value => 'abc 123/=?', # URL-encoded automatically
expires => '+7d', # epoch | +7d | +30m | +12h | now | session
secure => 1,
samesite => 'Lax', # HttpOnly + SameSite=Lax on by default
);
print $c->header, "\r\n";
# DELETE — expire it, matching the Path/Domain it was set with
print Cookie->new( name => 'session', path => '/' )->delete, "\r\n";
Why not just interpolate a string
Because cookie values are attacker-adjacent and the header is line-oriented.
- Injection-safe by default. Values are URL-encoded over UTF-8 octets, which
neutralises ;, CR, LF and the RFC separators while still round-tripping. A
raw => 1 value is still screened for header-breaking bytes.
- Names are validated as RFC 6265 tokens — no controls, whitespace or
separators.
- Attributes are screened — a Path or Domain containing a control character
or a semicolon is rejected rather than emitted.
- Prefixes are enforced.
__Secure-requires Secure;__Host-requires
Secure, Path=/ and no Domain. The class makes those true instead of trusting
you to remember.
SameSite=NoneimpliesSecure, because browsers will silently drop it
otherwise.
Expiry
expires accepts an epoch, a relative offset (+7d, +30m, +12h, +1y),
now, or session. Max-Age is emitted alongside Expires for older clients,
and the date is formatted locale-independently — a LANG change cannot produce
an unparseable cookie date.
In this site
Loom's admin session cookie and Tally's visitor cookie are both plain
Set-Cookie headers built on these rules. The
demo page lets you set attributes yourself and shows the
exact bytes that go on the wire.