-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).