nhttp_cookie (nhttp_lib v1.1.1)
View SourceCookie parsing and encoding utilities (RFC 6265).
This module provides symmetrical encode/decode functions for both Cookie and Set-Cookie headers.
Cookie Header (client → server)
The Cookie header contains simple name=value pairs separated by semicolons.
%% Decode incoming Cookie header
{ok, Cookies} = nhttp_cookie:decode_cookie(<<"session=abc123; user=john">>),
%% Cookies = [#{name => <<"session">>, value => <<"abc123">>},
%% #{name => <<"user">>, value => <<"john">>}]
{ok, CookieHeader} = nhttp_cookie:encode_cookie([
#{name => <<"session">>, value => <<"abc123">>},
#{name => <<"user">>, value => <<"john">>}
]),
%% <<"session=abc123; user=john">>Set-Cookie Header (server → client)
The Set-Cookie header contains a single cookie with optional attributes.
%% Decode incoming Set-Cookie header
{ok, SetCookie} = nhttp_cookie:decode_set_cookie(
<<"session=abc123; Path=/; HttpOnly; Secure">>
),
%% SetCookie = #{name => <<"session">>, value => <<"abc123">>,
%% path => <<"/">>, http_only => true, secure => true, ...}
%% Encode Set-Cookie header for responses
{ok, SetCookieHeader} = nhttp_cookie:encode_set_cookie(#{
name => <<"session">>,
value => <<"abc123">>,
path => <<"/">>,
max_age => 3600,
http_only => true,
secure => true,
same_site => strict
}),
%% <<"session=abc123; Path=/; Max-Age=3600; HttpOnly; Secure; SameSite=Strict">>Roundtrip Support
The decode output can be fed back to encode when it conforms to the RFC 6265 Section 4.1.1 grammar:
{ok, SetCookie} = nhttp_cookie:decode_set_cookie(Header),
%% Modify and re-encode
{ok, NewHeader} = nhttp_cookie:encode_set_cookie(SetCookie#{max_age => 7200}).The roundtrip is not total, and it must not be. The Section 5.2 parsing
algorithm is more permissive than the Section 4.1.1 grammar on purpose,
so decode_set_cookie/1 accepts a value that carries internal
whitespace, a comma, or a CR and LF pair. The encoders enforce the
grammar and refuse such a value with {error, Reason}. Re-emitting it
is the header injection that the validation exists to stop.
A forwarder must therefore pass the original field value through rather
than decode it and encode it again. The decoded map is lossy in the same
direction: decode_set_cookie/1 drops every attribute it does not name,
so a re-encode also discards the rest.
Summary
Functions
Decode a Cookie header value into a list of cookies. Parses a semicolon-separated Cookie header and returns a list of cookie maps. Empty names are rejected with an error.
Decode a Set-Cookie header value into a set-cookie map. Parses a Set-Cookie header with attributes and returns a map containing the cookie name, value, and any parsed attributes.
Encode a list of cookies into a Cookie header value. Takes a list of cookie maps and produces a semicolon-separated string suitable for the Cookie header.
Encode a set-cookie map into a Set-Cookie header value.
Takes a map with name and value (required) plus optional attributes,
and produces a Set-Cookie header string.
Types
-type cookie_error() :: empty_name | invalid_format | {invalid_cookie_name, name_violation()} | {invalid_cookie_value, value_violation()}.
-type domain_violation() :: empty | empty_label | invalid_label.
-type name_violation() :: empty | non_token_octet.
-type path_violation() :: empty | no_leading_slash | control_char | semicolon | non_ascii.
-type set_cookie_error() :: empty_name | invalid_format | {invalid_cookie_name, name_violation()} | {invalid_cookie_value, value_violation()} | {invalid_path, path_violation()} | {invalid_domain, domain_violation()}.
-type value_violation() :: control_char | separator | non_ascii | unbalanced_quote.
Functions
-spec decode_cookie(binary()) -> {ok, [t()]} | {error, cookie_error()}.
Decode a Cookie header value into a list of cookies. Parses a semicolon-separated Cookie header and returns a list of cookie maps. Empty names are rejected with an error.
{ok, Cookies} = nhttp_cookie:decode_cookie(<<"session=abc; user=john">>).
%% Cookies = [#{name => <<"session">>, value => <<"abc">>},
%% #{name => <<"user">>, value => <<"john">>}]
-spec decode_set_cookie(binary()) -> {ok, set_cookie()} | {error, set_cookie_error()}.
Decode a Set-Cookie header value into a set-cookie map. Parses a Set-Cookie header with attributes and returns a map containing the cookie name, value, and any parsed attributes.
{ok, SetCookie} = nhttp_cookie:decode_set_cookie(
<<"session=abc; Path=/; Secure; HttpOnly">>
).
%% SetCookie = #{name => <<"session">>, value => <<"abc">>,
%% path => <<"/">>, secure => true, http_only => true, ...}Invalid attributes are silently ignored (per RFC 6265 recommendations).
-spec encode_cookie([t()]) -> {ok, binary()} | {error, cookie_error()}.
Encode a list of cookies into a Cookie header value. Takes a list of cookie maps and produces a semicolon-separated string suitable for the Cookie header.
{ok, Header} = nhttp_cookie:encode_cookie([
#{name => <<"session">>, value => <<"abc">>},
#{name => <<"user">>, value => <<"john">>}
]).
%% Header = <<"session=abc; user=john">>Every pair is validated against RFC 6265 Section 4.1.1, the grammar that
Section 4.2.1 reuses for cookie-string. A name must be a token. A
value must be *cookie-octet, or *cookie-octet inside a matched pair
of double quotes, which admits %x21, %x23-2B, %x2D-3A, %x3C-5B,
and %x5D-7E. That excludes CTLs, space, double quote, comma,
semicolon, backslash, and every octet above %x7E.
A value that leaves the set is refused with
{error, {invalid_cookie_value, t:value_violation/0}}. No value is
stripped, quoted, escaped, or truncated, and the error carries a class
rather than the offending bytes, which are often a session token.
Encode arbitrary data with Base64 before you put it in a cookie.
-spec encode_set_cookie(set_cookie()) -> {ok, binary()} | {error, set_cookie_error()}.
Encode a set-cookie map into a Set-Cookie header value.
Takes a map with name and value (required) plus optional attributes,
and produces a Set-Cookie header string.
{ok, Header} = nhttp_cookie:encode_set_cookie(#{
name => <<"session">>,
value => <<"abc123">>,
path => <<"/">>,
max_age => 3600,
secure => true
}).
%% Header = <<"session=abc123; Path=/; Max-Age=3600; Secure">>Supported attributes: path, domain, expires, max_age, secure,
http_only, same_site.
The cookie pair is validated against the RFC 6265 Section 4.1.1 grammar.
A name must be a token. A value must be *cookie-octet, or
*cookie-octet inside a matched pair of double quotes, which admits
%x21, %x23-2B, %x2D-3A, %x3C-5B, and %x5D-7E. That excludes
CTLs, space, double quote, comma, semicolon, backslash, and every octet
above %x7E. Encode arbitrary data with Base64 before you put it in a
cookie.
The two attributes that carry caller data are validated too. path must
be <any CHAR except CTLs or ";"> and must start with /, because
Section 5.2.4 makes a user agent discard any other value. domain must
be a <subdomain> per RFC 1034 Section 3.5 and RFC 1123 Section 2.1:
dot-separated labels of letters, digits, and hyphens, with an optional
leading dot that Section 5.2.3 strips.
A field that leaves its grammar is refused with
{error, t:set_cookie_error/0}. Nothing is stripped, quoted, escaped,
or truncated, and an error carries a class rather than the offending
bytes, which are often a session token.