Current section

Files

Jump to
dream src dream@http@cookie.erl
Raw

src/dream@http@cookie.erl

-module(dream@http@cookie).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/dream/http/cookie.gleam").
-export([cookie_name/1, cookie_value/1, simple_cookie/2, secure_cookie/2, get_cookie/2, get_cookie_value/2, set_cookie/2, remove_cookie/2]).
-export_type([same_site/0, cookie/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" HTTP cookie types and utilities\n"
"\n"
" Types and functions for working with HTTP cookies. Cookies can be used\n"
" with both requests and responses.\n"
"\n"
" ## Quick Start\n"
"\n"
" ```gleam\n"
" import dream/http/cookie\n"
"\n"
" // Simple cookie\n"
" cookie.simple_cookie(\"session_id\", \"abc123\")\n"
"\n"
" // Secure cookie (recommended for sensitive data)\n"
" cookie.secure_cookie(\"auth_token\", \"xyz789\")\n"
"\n"
" // Get cookie from request\n"
" case cookie.get_cookie_value(request.cookies, \"session_id\") {\n"
" Some(id) -> // Use the session ID\n"
" None -> // No session cookie\n"
" }\n"
" ```\n"
"\n"
" ## Security\n"
"\n"
" For sensitive data (sessions, auth tokens), always use `secure_cookie()` which sets:\n"
" - `secure=True`: Only sent over HTTPS\n"
" - `httpOnly=True`: Not accessible to JavaScript (XSS protection)\n"
" - `sameSite=Strict`: Prevents CSRF attacks\n"
).
-type same_site() :: strict | lax | none.
-type cookie() :: {cookie,
binary(),
binary(),
gleam@option:option(integer()),
gleam@option:option(integer()),
gleam@option:option(binary()),
gleam@option:option(binary()),
boolean(),
boolean(),
gleam@option:option(same_site())}.
-file("src/dream/http/cookie.gleam", 130).
?DOC(
" Get the name of a cookie\n"
"\n"
" Extracts the name field from a Cookie.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/http/cookie\n"
"\n"
" let c = cookie.simple_cookie(\"session_id\", \"abc123\")\n"
" cookie.cookie_name(c) // \"session_id\"\n"
" ```\n"
).
-spec cookie_name(cookie()) -> binary().
cookie_name(Cookie) ->
{cookie, Name, _, _, _, _, _, _, _, _} = Cookie,
Name.
-file("src/dream/http/cookie.gleam", 147).
?DOC(
" Get the value of a cookie\n"
"\n"
" Extracts the value field from a Cookie.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/http/cookie\n"
"\n"
" let c = cookie.simple_cookie(\"session_id\", \"abc123\")\n"
" cookie.cookie_value(c) // \"abc123\"\n"
" ```\n"
).
-spec cookie_value(cookie()) -> binary().
cookie_value(Cookie) ->
{cookie, _, Value, _, _, _, _, _, _, _} = Cookie,
Value.
-file("src/dream/http/cookie.gleam", 174).
?DOC(
" Create a simple cookie with just name and value\n"
"\n"
" Creates an unsecured cookie with no expiration or security flags.\n"
" **Not recommended for sensitive data** - use `secure_cookie()` instead\n"
" for sessions, auth tokens, or any sensitive information.\n"
"\n"
" Use simple cookies for:\n"
" - User preferences (theme, language)\n"
" - Non-sensitive UI state\n"
" - Analytics tracking IDs\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/http/cookie\n"
"\n"
" // User preference cookie\n"
" cookie.simple_cookie(\"theme\", \"dark\")\n"
"\n"
" // Language preference\n"
" cookie.simple_cookie(\"lang\", \"en\")\n"
" ```\n"
).
-spec simple_cookie(binary(), binary()) -> cookie().
simple_cookie(Name, Value) ->
{cookie, Name, Value, none, none, none, none, false, false, none}.
-file("src/dream/http/cookie.gleam", 218).
?DOC(
" Create a secure cookie for sensitive data\n"
"\n"
" Creates a cookie with security best practices enabled:\n"
" - `secure=True`: Only sent over HTTPS connections\n"
" - `httpOnly=True`: Not accessible to JavaScript (prevents XSS attacks)\n"
" - `sameSite=Strict`: Not sent with cross-site requests (prevents CSRF)\n"
"\n"
" **Always use this for sensitive data** like session IDs, authentication tokens,\n"
" CSRF tokens, or any data that could be used to impersonate a user.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/http/cookie\n"
"\n"
" // Session cookie\n"
" cookie.secure_cookie(\"session_id\", generate_session_id())\n"
"\n"
" // Authentication token\n"
" cookie.secure_cookie(\"auth_token\", jwt_token)\n"
"\n"
" // CSRF token\n"
" cookie.secure_cookie(\"csrf_token\", generate_csrf_token())\n"
" ```\n"
"\n"
" ## Security Note\n"
"\n"
" The `httpOnly` flag prevents JavaScript from accessing the cookie,\n"
" which protects against XSS attacks where malicious scripts try to\n"
" steal session tokens.\n"
).
-spec secure_cookie(binary(), binary()) -> cookie().
secure_cookie(Name, Value) ->
{cookie, Name, Value, none, none, none, none, true, true, {some, strict}}.
-file("src/dream/http/cookie.gleam", 261).
-spec find_cookie(list(cookie()), binary()) -> gleam@option:option(cookie()).
find_cookie(Cookies, Normalized_name) ->
case Cookies of
[] ->
none;
[Cookie | Rest] ->
Cookie_normalized = string:lowercase(cookie_name(Cookie)),
Matches = Cookie_normalized =:= Normalized_name,
case Matches of
true ->
{some, Cookie};
false ->
find_cookie(Rest, Normalized_name)
end
end.
-file("src/dream/http/cookie.gleam", 256).
?DOC(
" Get a cookie by name (case-insensitive)\n"
"\n"
" Searches a list of cookies for one matching the given name.\n"
" Cookie name comparison is case-insensitive.\n"
" Returns the first matching cookie.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/http/cookie\n"
"\n"
" let cookies = [\n"
" cookie.simple_cookie(\"theme\", \"dark\"),\n"
" cookie.secure_cookie(\"session_id\", \"abc123\"),\n"
" ]\n"
"\n"
" case cookie.get_cookie(cookies, \"session_id\") {\n"
" Some(c) -> cookie.cookie_value(c) // \"abc123\"\n"
" None -> \"no session\"\n"
" }\n"
"\n"
" // Case-insensitive\n"
" cookie.get_cookie(cookies, \"THEME\") // Some(Cookie(\"theme\", \"dark\", ...))\n"
" ```\n"
).
-spec get_cookie(list(cookie()), binary()) -> gleam@option:option(cookie()).
get_cookie(Cookies, Name) ->
Normalized_name = string:lowercase(Name),
find_cookie(Cookies, Normalized_name).
-file("src/dream/http/cookie.gleam", 296).
?DOC(
" Get a cookie value by name\n"
"\n"
" Convenience function that searches for a cookie and returns just its value.\n"
" Returns None if the cookie doesn't exist.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/http/cookie\n"
"\n"
" let cookies = [\n"
" cookie.simple_cookie(\"theme\", \"dark\"),\n"
" cookie.secure_cookie(\"session_id\", \"abc123\"),\n"
" ]\n"
"\n"
" cookie.get_cookie_value(cookies, \"theme\") // Some(\"dark\")\n"
" cookie.get_cookie_value(cookies, \"missing\") // None\n"
" ```\n"
).
-spec get_cookie_value(list(cookie()), binary()) -> gleam@option:option(binary()).
get_cookie_value(Cookies, Name) ->
case get_cookie(Cookies, Name) of
{some, Cookie} ->
{some, cookie_value(Cookie)};
none ->
none
end.
-file("src/dream/http/cookie.gleam", 349).
-spec filter_cookies_recursive(list(cookie()), binary(), list(cookie())) -> list(cookie()).
filter_cookies_recursive(Cookies, Normalized_name, Acc) ->
case Cookies of
[] ->
lists:reverse(Acc);
[Cookie | Rest] ->
Cookie_normalized = string:lowercase(cookie_name(Cookie)),
Should_keep = Cookie_normalized /= Normalized_name,
case Should_keep of
true ->
filter_cookies_recursive(
Rest,
Normalized_name,
[Cookie | Acc]
);
false ->
filter_cookies_recursive(Rest, Normalized_name, Acc)
end
end.
-file("src/dream/http/cookie.gleam", 342).
-spec filter_matching_cookies(list(cookie()), binary()) -> list(cookie()).
filter_matching_cookies(Cookies, Normalized_name) ->
filter_cookies_recursive(Cookies, Normalized_name, []).
-file("src/dream/http/cookie.gleam", 336).
?DOC(
" Set or replace a cookie\n"
"\n"
" If a cookie with this name exists (case-insensitive), replaces it.\n"
" If not, adds a new cookie. Only one cookie with the given name will\n"
" exist in the result.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/http/cookie\n"
"\n"
" let cookies = [cookie.simple_cookie(\"theme\", \"light\")]\n"
"\n"
" // Replace existing cookie\n"
" let updated = cookie.set_cookie(\n"
" cookies,\n"
" cookie.simple_cookie(\"theme\", \"dark\")\n"
" )\n"
" // Result: [Cookie(\"theme\", \"dark\", ...)]\n"
"\n"
" // Add new cookie\n"
" let with_session = cookie.set_cookie(\n"
" updated,\n"
" cookie.secure_cookie(\"session_id\", \"abc123\")\n"
" )\n"
" // Result: [\n"
" // Cookie(\"session_id\", \"abc123\", ...),\n"
" // Cookie(\"theme\", \"dark\", ...)\n"
" // ]\n"
" ```\n"
).
-spec set_cookie(list(cookie()), cookie()) -> list(cookie()).
set_cookie(Cookies, Cookie) ->
Normalized_name = string:lowercase(cookie_name(Cookie)),
Filtered = filter_matching_cookies(Cookies, Normalized_name),
[Cookie | Filtered].
-file("src/dream/http/cookie.gleam", 397).
?DOC(
" Remove a cookie by name (case-insensitive)\n"
"\n"
" Removes all cookies with the given name (case-insensitive).\n"
" Returns a new list with matching cookies filtered out.\n"
"\n"
" To delete a cookie in the browser, you typically need to set it with\n"
" an expired date or max-age=0, not just remove it from the list.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/http/cookie\n"
"\n"
" let cookies = [\n"
" cookie.simple_cookie(\"theme\", \"dark\"),\n"
" cookie.secure_cookie(\"session_id\", \"abc123\"),\n"
" ]\n"
"\n"
" // Remove theme cookie from list\n"
" let filtered = cookie.remove_cookie(cookies, \"theme\")\n"
" // Result: [Cookie(\"session_id\", \"abc123\", ...)]\n"
"\n"
" // To delete in browser, set expired cookie:\n"
" let expired = Cookie(\n"
" name: \"session_id\",\n"
" value: \"\",\n"
" max_age: option.Some(0), // Expire immediately\n"
" // ... other fields\n"
" )\n"
" ```\n"
).
-spec remove_cookie(list(cookie()), binary()) -> list(cookie()).
remove_cookie(Cookies, Name) ->
Normalized_name = string:lowercase(Name),
filter_matching_cookies(Cookies, Normalized_name).