Skip to main content

src/plume@content_security_policy.erl

-module(plume@content_security_policy).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/plume/content_security_policy.gleam").
-export([to_string/1]).
-export_type([content_security_policy/0, directive/0, source/0, trusted_types_sink/0, trusted_types_policy/0, sandbox_token/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(
    " Content-Security-Policy (CSP)\n"
    "\n"
    " This response header lets sites declare which resources the browser is\n"
    " allowed to load for a given page, mitigating cross-site scripting (XSS)\n"
    " and data-injection attacks. `plume.default()` ships a sensible starter\n"
    " policy.\n"
    "\n"
    " Most directives expect at least one source. Passing an empty list (e.g.\n"
    " `DefaultSrc([])`) renders an incomplete directive — omit it entirely\n"
    " instead. `Sandbox([])` is the exception; an empty token list applies the\n"
    " maximum restrictions.\n"
    "\n"
    " ## Examples\n"
    "\n"
    " ```gleam\n"
    " Policy([\n"
    "   DefaultSrc([Self]),\n"
    "   ScriptSrc([Self]),\n"
    "   ImgSrc([Self, Scheme(\"data\")]),\n"
    "   StyleSrc([Self, UnsafeInline]),\n"
    " ])\n"
    " ```\n"
    "\n"
    " See the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy).\n"
).

-type content_security_policy() :: {policy, list(directive())}.

-type directive() :: {default_src, list(source())} |
    {script_src, list(source())} |
    {script_src_attr, list(source())} |
    {script_src_elem, list(source())} |
    {style_src, list(source())} |
    {style_src_attr, list(source())} |
    {style_src_elem, list(source())} |
    {img_src, list(source())} |
    {connect_src, list(source())} |
    {font_src, list(source())} |
    {object_src, list(source())} |
    {media_src, list(source())} |
    {frame_src, list(source())} |
    {fenced_frame_src, list(source())} |
    {frame_ancestors, list(source())} |
    {child_src, list(source())} |
    {manifest_src, list(source())} |
    {worker_src, list(source())} |
    {base_uri, list(source())} |
    {form_action, list(source())} |
    {sandbox, list(sandbox_token())} |
    upgrade_insecure_requests |
    {require_trusted_types_for, list(trusted_types_sink())} |
    {trusted_types, list(trusted_types_policy())}.

-type source() :: self |
    none |
    unsafe_inline |
    unsafe_eval |
    strict_dynamic |
    wasm_unsafe_eval |
    unsafe_hashes |
    inline_speculation_rules |
    wildcard |
    {host, binary()} |
    {scheme, binary()} |
    {nonce, binary()} |
    {sha256, binary()} |
    {sha384, binary()} |
    {sha512, binary()}.

-type trusted_types_sink() :: script.

-type trusted_types_policy() :: {policy_name, binary()} |
    allow_duplicates |
    no_policy |
    any_policy.

-type sandbox_token() :: allow_downloads |
    allow_forms |
    allow_modals |
    allow_orientation_lock |
    allow_pointer_lock |
    allow_popups |
    allow_popups_to_escape_sandbox |
    allow_presentation |
    allow_same_origin |
    allow_scripts |
    allow_top_navigation |
    allow_top_navigation_by_user_activation |
    allow_top_navigation_to_custom_protocols |
    allow_storage_access_by_user_activation.

-file("src/plume/content_security_policy.gleam", 295).
-spec trusted_types_policy_to_string(trusted_types_policy()) -> binary().
trusted_types_policy_to_string(Policy) ->
    case Policy of
        {policy_name, Name} ->
            Name;

        allow_duplicates ->
            <<"'allow-duplicates'"/utf8>>;

        no_policy ->
            <<"'none'"/utf8>>;

        any_policy ->
            <<"*"/utf8>>
    end.

-file("src/plume/content_security_policy.gleam", 289).
-spec trusted_types_sink_to_string(trusted_types_sink()) -> binary().
trusted_types_sink_to_string(Sink) ->
    case Sink of
        script ->
            <<"'script'"/utf8>>
    end.

-file("src/plume/content_security_policy.gleam", 304).
-spec sandbox_token_to_string(sandbox_token()) -> binary().
sandbox_token_to_string(Token) ->
    case Token of
        allow_downloads ->
            <<"allow-downloads"/utf8>>;

        allow_forms ->
            <<"allow-forms"/utf8>>;

        allow_modals ->
            <<"allow-modals"/utf8>>;

        allow_orientation_lock ->
            <<"allow-orientation-lock"/utf8>>;

        allow_pointer_lock ->
            <<"allow-pointer-lock"/utf8>>;

        allow_popups ->
            <<"allow-popups"/utf8>>;

        allow_popups_to_escape_sandbox ->
            <<"allow-popups-to-escape-sandbox"/utf8>>;

        allow_presentation ->
            <<"allow-presentation"/utf8>>;

        allow_same_origin ->
            <<"allow-same-origin"/utf8>>;

        allow_scripts ->
            <<"allow-scripts"/utf8>>;

        allow_top_navigation ->
            <<"allow-top-navigation"/utf8>>;

        allow_top_navigation_by_user_activation ->
            <<"allow-top-navigation-by-user-activation"/utf8>>;

        allow_top_navigation_to_custom_protocols ->
            <<"allow-top-navigation-to-custom-protocols"/utf8>>;

        allow_storage_access_by_user_activation ->
            <<"allow-storage-access-by-user-activation"/utf8>>
    end.

-file("src/plume/content_security_policy.gleam", 265).
-spec render_tokens(binary(), list(sandbox_token())) -> binary().
render_tokens(Name, Tokens) ->
    gleam@string:join(
        [Name | gleam@list:map(Tokens, fun sandbox_token_to_string/1)],
        <<" "/utf8>>
    ).

-file("src/plume/content_security_policy.gleam", 269).
-spec source_to_string(source()) -> binary().
source_to_string(Source) ->
    case Source of
        self ->
            <<"'self'"/utf8>>;

        none ->
            <<"'none'"/utf8>>;

        unsafe_inline ->
            <<"'unsafe-inline'"/utf8>>;

        unsafe_eval ->
            <<"'unsafe-eval'"/utf8>>;

        strict_dynamic ->
            <<"'strict-dynamic'"/utf8>>;

        wasm_unsafe_eval ->
            <<"'wasm-unsafe-eval'"/utf8>>;

        unsafe_hashes ->
            <<"'unsafe-hashes'"/utf8>>;

        inline_speculation_rules ->
            <<"'inline-speculation-rules'"/utf8>>;

        wildcard ->
            <<"*"/utf8>>;

        {host, Value} ->
            Value;

        {scheme, Value@1} ->
            <<Value@1/binary, ":"/utf8>>;

        {nonce, Value@2} ->
            <<<<"'nonce-"/utf8, Value@2/binary>>/binary, "'"/utf8>>;

        {sha256, Value@3} ->
            <<<<"'sha256-"/utf8, Value@3/binary>>/binary, "'"/utf8>>;

        {sha384, Value@4} ->
            <<<<"'sha384-"/utf8, Value@4/binary>>/binary, "'"/utf8>>;

        {sha512, Value@5} ->
            <<<<"'sha512-"/utf8, Value@5/binary>>/binary, "'"/utf8>>
    end.

-file("src/plume/content_security_policy.gleam", 261).
-spec render_sources(binary(), list(source())) -> binary().
render_sources(Name, Sources) ->
    gleam@string:join(
        [Name | gleam@list:map(Sources, fun source_to_string/1)],
        <<" "/utf8>>
    ).

-file("src/plume/content_security_policy.gleam", 221).
-spec directive_to_string(directive()) -> binary().
directive_to_string(Directive) ->
    case Directive of
        {default_src, Sources} ->
            render_sources(<<"default-src"/utf8>>, Sources);

        {script_src, Sources@1} ->
            render_sources(<<"script-src"/utf8>>, Sources@1);

        {script_src_attr, Sources@2} ->
            render_sources(<<"script-src-attr"/utf8>>, Sources@2);

        {script_src_elem, Sources@3} ->
            render_sources(<<"script-src-elem"/utf8>>, Sources@3);

        {style_src, Sources@4} ->
            render_sources(<<"style-src"/utf8>>, Sources@4);

        {style_src_attr, Sources@5} ->
            render_sources(<<"style-src-attr"/utf8>>, Sources@5);

        {style_src_elem, Sources@6} ->
            render_sources(<<"style-src-elem"/utf8>>, Sources@6);

        {img_src, Sources@7} ->
            render_sources(<<"img-src"/utf8>>, Sources@7);

        {connect_src, Sources@8} ->
            render_sources(<<"connect-src"/utf8>>, Sources@8);

        {font_src, Sources@9} ->
            render_sources(<<"font-src"/utf8>>, Sources@9);

        {object_src, Sources@10} ->
            render_sources(<<"object-src"/utf8>>, Sources@10);

        {media_src, Sources@11} ->
            render_sources(<<"media-src"/utf8>>, Sources@11);

        {frame_src, Sources@12} ->
            render_sources(<<"frame-src"/utf8>>, Sources@12);

        {fenced_frame_src, Sources@13} ->
            render_sources(<<"fenced-frame-src"/utf8>>, Sources@13);

        {frame_ancestors, Sources@14} ->
            render_sources(<<"frame-ancestors"/utf8>>, Sources@14);

        {child_src, Sources@15} ->
            render_sources(<<"child-src"/utf8>>, Sources@15);

        {manifest_src, Sources@16} ->
            render_sources(<<"manifest-src"/utf8>>, Sources@16);

        {worker_src, Sources@17} ->
            render_sources(<<"worker-src"/utf8>>, Sources@17);

        {base_uri, Sources@18} ->
            render_sources(<<"base-uri"/utf8>>, Sources@18);

        {form_action, Sources@19} ->
            render_sources(<<"form-action"/utf8>>, Sources@19);

        {sandbox, Tokens} ->
            render_tokens(<<"sandbox"/utf8>>, Tokens);

        upgrade_insecure_requests ->
            <<"upgrade-insecure-requests"/utf8>>;

        {require_trusted_types_for, Sinks} ->
            gleam@string:join(
                [<<"require-trusted-types-for"/utf8>> |
                    gleam@list:map(Sinks, fun trusted_types_sink_to_string/1)],
                <<" "/utf8>>
            );

        {trusted_types, Policies} ->
            gleam@string:join(
                [<<"trusted-types"/utf8>> |
                    gleam@list:map(
                        Policies,
                        fun trusted_types_policy_to_string/1
                    )],
                <<" "/utf8>>
            )
    end.

-file("src/plume/content_security_policy.gleam", 214).
?DOC(" Encode as the `Content-Security-Policy` header value.\n").
-spec to_string(content_security_policy()) -> binary().
to_string(Value) ->
    {policy, Directives} = Value,
    _pipe = Directives,
    _pipe@1 = gleam@list:map(_pipe, fun directive_to_string/1),
    gleam@string:join(_pipe@1, <<"; "/utf8>>).