Skip to main content

src/fcgi@internal@responder.erl

-module(fcgi@internal@responder).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/fcgi/internal/responder.gleam").
-export([encode_overloaded_end/1, step/3, encode_stdout_chunk/2, encode_response_headers/2, encode_response_end_records/1]).
-export_type([next/0, event/0, outcome/0, state/0, receiving_state/0, decision/0, transition/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(false).

-type next() :: wait_for_more | close_connection.

-type event() :: {request_ready, integer(), bitstring(), boolean()} |
    {body_chunk, bitstring()} |
    body_end |
    body_too_large.

-type outcome() :: {outcome,
        state(),
        gleam@bytes_tree:bytes_tree(),
        list(event()),
        next()}.

-type state() :: {idle, bitstring()} | {receiving, receiving_state()}.

-type receiving_state() :: {receiving_state,
        bitstring(),
        integer(),
        boolean(),
        bitstring(),
        boolean(),
        integer(),
        boolean(),
        boolean(),
        boolean()}.

-type decision() :: keep_parsing | {halt, next()}.

-type transition() :: {transition,
        state(),
        gleam@bytes_tree:bytes_tree(),
        list(event()),
        decision()}.

-file("src/fcgi/internal/responder.gleam", 133).
?DOC(false).
-spec is_request_boundary(state(), state()) -> boolean().
is_request_boundary(Before, After_state) ->
    case {Before, After_state} of
        {{receiving, _}, {idle, _}} ->
            true;

        {_, _} ->
            false
    end.

-file("src/fcgi/internal/responder.gleam", 180).
?DOC(false).
-spec skip_record(state()) -> transition().
skip_record(State) ->
    {transition, State, gleam@bytes_tree:new(), [], keep_parsing}.

-file("src/fcgi/internal/responder.gleam", 444).
?DOC(false).
-spec lookup_capability(binary()) -> {ok, {binary(), binary()}} | {error, nil}.
lookup_capability(Name) ->
    case Name of
        <<"FCGI_MPXS_CONNS"/utf8>> ->
            {ok, {Name, <<"0"/utf8>>}};

        _ ->
            {error, nil}
    end.

-file("src/fcgi/internal/responder.gleam", 435).
?DOC(false).
-spec handle_get_values(state(), list(binary())) -> transition().
handle_get_values(State, Names) ->
    Pairs = gleam@list:filter_map(Names, fun lookup_capability/1),
    Reply = fcgi@internal@protocol:encode_record({get_values_result, Pairs}),
    {transition, State, Reply, [], keep_parsing}.

-file("src/fcgi/internal/responder.gleam", 419).
?DOC(false).
-spec handle_abort(integer()) -> transition().
handle_abort(Request_id) ->
    Reply = fcgi@internal@protocol:encode_record(
        {end_request, Request_id, 0, request_complete}
    ),
    {transition, {idle, <<>>}, Reply, [], {halt, close_connection}}.

-file("src/fcgi/internal/responder.gleam", 376).
?DOC(false).
-spec handle_stdin_in_bounds(receiving_state(), bitstring(), integer()) -> transition().
handle_stdin_in_bounds(Recv, Data, New_total) ->
    Updated = {receiving,
        {receiving_state,
            erlang:element(2, Recv),
            erlang:element(3, Recv),
            erlang:element(4, Recv),
            erlang:element(5, Recv),
            erlang:element(6, Recv),
            New_total,
            erlang:element(8, Recv),
            erlang:element(9, Recv),
            erlang:element(10, Recv)}},
    case erlang:element(6, Recv) of
        true ->
            {transition,
                Updated,
                gleam@bytes_tree:new(),
                [{body_chunk, Data}],
                keep_parsing};

        false ->
            {transition, Updated, gleam@bytes_tree:new(), [], keep_parsing}
    end.

-file("src/fcgi/internal/responder.gleam", 487).
?DOC(false).
-spec encode_overloaded_end(integer()) -> gleam@bytes_tree:bytes_tree().
encode_overloaded_end(Request_id) ->
    fcgi@internal@protocol:encode_record(
        {end_request, Request_id, 0, overloaded}
    ).

-file("src/fcgi/internal/responder.gleam", 400).
?DOC(false).
-spec handle_stdin_overflow(receiving_state()) -> transition().
handle_stdin_overflow(Recv) ->
    case erlang:element(6, Recv) of
        true ->
            {transition,
                {receiving,
                    {receiving_state,
                        erlang:element(2, Recv),
                        erlang:element(3, Recv),
                        erlang:element(4, Recv),
                        erlang:element(5, Recv),
                        erlang:element(6, Recv),
                        erlang:element(7, Recv),
                        erlang:element(8, Recv),
                        true,
                        erlang:element(10, Recv)}},
                gleam@bytes_tree:new(),
                [body_too_large],
                keep_parsing};

        false ->
            {transition,
                {idle, erlang:element(2, Recv)},
                encode_overloaded_end(erlang:element(3, Recv)),
                [],
                {halt, close_connection}}
    end.

-file("src/fcgi/internal/responder.gleam", 354).
?DOC(false).
-spec handle_stdin_chunk(receiving_state(), bitstring(), integer()) -> transition().
handle_stdin_chunk(Recv, Data, Max_body_size) ->
    gleam@bool:guard(
        erlang:element(9, Recv),
        {transition,
            {receiving, Recv},
            gleam@bytes_tree:new(),
            [],
            keep_parsing},
        fun() ->
            New_total = erlang:element(7, Recv) + erlang:byte_size(Data),
            case New_total > Max_body_size of
                true ->
                    handle_stdin_overflow(Recv);

                false ->
                    handle_stdin_in_bounds(Recv, Data, New_total)
            end
        end
    ).

-file("src/fcgi/internal/responder.gleam", 335).
?DOC(false).
-spec finish_stdin(receiving_state()) -> transition().
finish_stdin(Recv) ->
    case erlang:element(6, Recv) of
        true ->
            {transition,
                {idle, erlang:element(2, Recv)},
                gleam@bytes_tree:new(),
                [body_end],
                keep_parsing};

        false ->
            {transition,
                {receiving,
                    {receiving_state,
                        erlang:element(2, Recv),
                        erlang:element(3, Recv),
                        erlang:element(4, Recv),
                        erlang:element(5, Recv),
                        erlang:element(6, Recv),
                        erlang:element(7, Recv),
                        true,
                        erlang:element(9, Recv),
                        erlang:element(10, Recv)}},
                gleam@bytes_tree:new(),
                [],
                keep_parsing}
    end.

-file("src/fcgi/internal/responder.gleam", 324).
?DOC(false).
-spec handle_stdin(receiving_state(), bitstring(), integer()) -> transition().
handle_stdin(Recv, Data, Max_body_size) ->
    case erlang:byte_size(Data) of
        0 ->
            finish_stdin(Recv);

        _ ->
            handle_stdin_chunk(Recv, Data, Max_body_size)
    end.

-file("src/fcgi/internal/responder.gleam", 271).
?DOC(false).
-spec merge_input(bitstring(), boolean(), bitstring(), integer()) -> {bitstring(),
    boolean()}.
merge_input(Current, Overflow, Data, Max) ->
    gleam@bool:guard(
        Overflow,
        {<<>>, true},
        fun() ->
            Combined = <<Current/bitstring, Data/bitstring>>,
            case erlang:byte_size(Combined) > Max of
                true ->
                    {<<>>, true};

                false ->
                    {Combined, false}
            end
        end
    ).

-file("src/fcgi/internal/responder.gleam", 298).
?DOC(false).
-spec emit_request_ready(receiving_state()) -> transition().
emit_request_ready(Recv) ->
    Ready_event = {request_ready,
        erlang:element(3, Recv),
        erlang:element(5, Recv),
        erlang:element(4, Recv)},
    case erlang:element(8, Recv) of
        true ->
            {transition,
                {idle, erlang:element(2, Recv)},
                gleam@bytes_tree:new(),
                [Ready_event, body_end],
                keep_parsing};

        false ->
            {transition,
                {receiving,
                    {receiving_state,
                        erlang:element(2, Recv),
                        erlang:element(3, Recv),
                        erlang:element(4, Recv),
                        erlang:element(5, Recv),
                        true,
                        erlang:element(7, Recv),
                        erlang:element(8, Recv),
                        erlang:element(9, Recv),
                        erlang:element(10, Recv)}},
                gleam@bytes_tree:new(),
                [Ready_event],
                keep_parsing}
    end.

-file("src/fcgi/internal/responder.gleam", 285).
?DOC(false).
-spec finish_params(receiving_state()) -> transition().
finish_params(Recv) ->
    case erlang:element(10, Recv) of
        true ->
            {transition,
                {idle, <<>>},
                encode_overloaded_end(erlang:element(3, Recv)),
                [],
                {halt, close_connection}};

        false ->
            emit_request_ready(Recv)
    end.

-file("src/fcgi/internal/responder.gleam", 247).
?DOC(false).
-spec handle_params(receiving_state(), bitstring()) -> transition().
handle_params(Recv, Data) ->
    case erlang:byte_size(Data) of
        0 ->
            finish_params(Recv);

        _ ->
            {Params, Overflow} = merge_input(
                erlang:element(5, Recv),
                erlang:element(10, Recv),
                Data,
                65535
            ),
            {transition,
                {receiving,
                    {receiving_state,
                        erlang:element(2, Recv),
                        erlang:element(3, Recv),
                        erlang:element(4, Recv),
                        Params,
                        erlang:element(6, Recv),
                        erlang:element(7, Recv),
                        erlang:element(8, Recv),
                        erlang:element(9, Recv),
                        Overflow}},
                gleam@bytes_tree:new(),
                [],
                keep_parsing}
    end.

-file("src/fcgi/internal/responder.gleam", 231).
?DOC(false).
-spec handle_begin_request_busy(receiving_state(), integer()) -> transition().
handle_begin_request_busy(Recv, Id) ->
    Reply = fcgi@internal@protocol:encode_record(
        {end_request, Id, 0, cant_multiplex_connection}
    ),
    {transition, {receiving, Recv}, Reply, [], keep_parsing}.

-file("src/fcgi/internal/responder.gleam", 189).
?DOC(false).
-spec handle_begin_request_idle(bitstring(), integer(), integer(), boolean()) -> transition().
handle_begin_request_idle(Buffer, Id, Role, Keep) ->
    case Role =:= 1 of
        true ->
            {transition,
                {receiving,
                    {receiving_state,
                        Buffer,
                        Id,
                        Keep,
                        <<>>,
                        false,
                        0,
                        false,
                        false,
                        false}},
                gleam@bytes_tree:new(),
                [],
                keep_parsing};

        false ->
            Reply = fcgi@internal@protocol:encode_record(
                {end_request, Id, 0, unknown_role}
            ),
            {transition, {idle, Buffer}, Reply, [], {halt, close_connection}}
    end.

-file("src/fcgi/internal/responder.gleam", 140).
?DOC(false).
-spec handle_record(state(), fcgi@internal@protocol:incoming(), integer()) -> transition().
handle_record(State, Record, Max_body_size) ->
    case {State, Record} of
        {_, {begin_request, 0, _, _}} ->
            {transition,
                State,
                gleam@bytes_tree:new(),
                [],
                {halt, close_connection}};

        {{idle, Buffer}, {begin_request, Id, Role, Keep}} ->
            handle_begin_request_idle(Buffer, Id, Role, Keep);

        {{receiving, Recv}, {begin_request, Id@1, _, _}} ->
            handle_begin_request_busy(Recv, Id@1);

        {{receiving, Recv@1}, {params, Id@2, Data}} when (Id@2 =:= erlang:element(
            3,
            Recv@1
        )) andalso not erlang:element(6, Recv@1) ->
            handle_params(Recv@1, Data);

        {{receiving, Recv@2}, {stdin, Id@3, Data@1}} when Id@3 =:= erlang:element(
            3,
            Recv@2
        ) ->
            handle_stdin(Recv@2, Data@1, Max_body_size);

        {{receiving, Recv@3}, {abort_request, Id@4}} when Id@4 =:= erlang:element(
            3,
            Recv@3
        ) ->
            handle_abort(erlang:element(3, Recv@3));

        {_, {get_values, Names}} ->
            handle_get_values(State, Names);

        {_, {incoming_unknown, 0, Type_byte}} ->
            Reply = fcgi@internal@protocol:encode_record(
                {unknown_type, Type_byte}
            ),
            {transition, State, Reply, [], keep_parsing};

        {_, {params, _, _}} ->
            skip_record(State);

        {_, {stdin, _, _}} ->
            skip_record(State);

        {_, {abort_request, _}} ->
            skip_record(State);

        {_, {incoming_unknown, _, _}} ->
            skip_record(State)
    end.

-file("src/fcgi/internal/responder.gleam", 75).
?DOC(false).
-spec step_loop(
    state(),
    gleam@bytes_tree:bytes_tree(),
    list(event()),
    integer()
) -> outcome().
step_loop(State, Outgoing, Events_rev, Max_body_size) ->
    Buffer = case State of
        {idle, Buf} ->
            Buf;

        {receiving, Recv} ->
            erlang:element(2, Recv)
    end,
    case fcgi@internal@protocol:parse_record(Buffer) of
        need_more ->
            {outcome, State, Outgoing, lists:reverse(Events_rev), wait_for_more};

        {parse_error, _} ->
            {outcome,
                State,
                Outgoing,
                lists:reverse(Events_rev),
                close_connection};

        {parsed, Record, Rest} ->
            Action = handle_record(State, Record, Max_body_size),
            Advanced = case erlang:element(2, Action) of
                {idle, _} ->
                    {idle, Rest};

                {receiving, Recv@1} ->
                    {receiving,
                        {receiving_state,
                            Rest,
                            erlang:element(3, Recv@1),
                            erlang:element(4, Recv@1),
                            erlang:element(5, Recv@1),
                            erlang:element(6, Recv@1),
                            erlang:element(7, Recv@1),
                            erlang:element(8, Recv@1),
                            erlang:element(9, Recv@1),
                            erlang:element(10, Recv@1)}}
            end,
            Combined_outgoing = gleam_stdlib:iodata_append(
                Outgoing,
                erlang:element(3, Action)
            ),
            Combined_events = gleam@list:fold(
                erlang:element(4, Action),
                Events_rev,
                fun(Acc, Evt) -> [Evt | Acc] end
            ),
            case {erlang:element(5, Action),
                is_request_boundary(State, erlang:element(2, Action))} of
                {{halt, Next}, _} ->
                    {outcome,
                        Advanced,
                        Combined_outgoing,
                        lists:reverse(Combined_events),
                        Next};

                {keep_parsing, true} ->
                    {outcome,
                        Advanced,
                        Combined_outgoing,
                        lists:reverse(Combined_events),
                        wait_for_more};

                {keep_parsing, false} ->
                    step_loop(
                        Advanced,
                        Combined_outgoing,
                        Combined_events,
                        Max_body_size
                    )
            end
    end.

-file("src/fcgi/internal/responder.gleam", 59).
?DOC(false).
-spec step(state(), bitstring(), integer()) -> outcome().
step(State, Bytes, Max_body_size) ->
    Combined = case State of
        {idle, Buf} ->
            {idle, <<Buf/bitstring, Bytes/bitstring>>};

        {receiving, Recv} ->
            {receiving,
                {receiving_state,
                    <<(erlang:element(2, Recv))/bitstring, Bytes/bitstring>>,
                    erlang:element(3, Recv),
                    erlang:element(4, Recv),
                    erlang:element(5, Recv),
                    erlang:element(6, Recv),
                    erlang:element(7, Recv),
                    erlang:element(8, Recv),
                    erlang:element(9, Recv),
                    erlang:element(10, Recv)}}
    end,
    step_loop(Combined, gleam@bytes_tree:new(), [], Max_body_size).

-file("src/fcgi/internal/responder.gleam", 528).
?DOC(false).
-spec contains_crlf(binary()) -> boolean().
contains_crlf(Value) ->
    gleam_stdlib:contains_string(Value, <<"\r"/utf8>>) orelse gleam_stdlib:contains_string(
        Value,
        <<"\n"/utf8>>
    ).

-file("src/fcgi/internal/responder.gleam", 524).
?DOC(false).
-spec is_safe_header(binary(), binary()) -> boolean().
is_safe_header(Name, Value) ->
    not contains_crlf(Name) andalso not contains_crlf(Value).

-file("src/fcgi/internal/responder.gleam", 501).
?DOC(false).
-spec encode_http_headers(gleam@http@response:response(any())) -> gleam@bytes_tree:bytes_tree().
encode_http_headers(Resp) ->
    Start = begin
        _pipe = gleam_stdlib:wrap_list(<<"Status: "/utf8>>),
        _pipe@1 = gleam@bytes_tree:append_string(
            _pipe,
            erlang:integer_to_binary(erlang:element(2, Resp))
        ),
        gleam@bytes_tree:append_string(_pipe@1, <<"\r\n"/utf8>>)
    end,
    With_headers = gleam@list:fold(
        erlang:element(3, Resp),
        Start,
        fun(Acc, Header) ->
            {Name, Value} = Header,
            case is_safe_header(Name, Value) of
                true ->
                    _pipe@2 = Acc,
                    _pipe@3 = gleam@bytes_tree:append_string(_pipe@2, Name),
                    _pipe@4 = gleam@bytes_tree:append_string(
                        _pipe@3,
                        <<": "/utf8>>
                    ),
                    _pipe@5 = gleam@bytes_tree:append_string(_pipe@4, Value),
                    gleam@bytes_tree:append_string(_pipe@5, <<"\r\n"/utf8>>);

                false ->
                    Acc
            end
        end
    ),
    gleam@bytes_tree:append_string(With_headers, <<"\r\n"/utf8>>).

-file("src/fcgi/internal/responder.gleam", 495).
?DOC(false).
-spec encode_records(list(fcgi@internal@protocol:outgoing())) -> gleam@bytes_tree:bytes_tree().
encode_records(Records) ->
    gleam@list:fold(
        Records,
        gleam@bytes_tree:new(),
        fun(Acc, Record) ->
            gleam_stdlib:iodata_append(
                Acc,
                fcgi@internal@protocol:encode_record(Record)
            )
        end
    ).

-file("src/fcgi/internal/responder.gleam", 469).
?DOC(false).
-spec encode_stdout_chunk(integer(), gleam@bytes_tree:bytes_tree()) -> gleam@bytes_tree:bytes_tree().
encode_stdout_chunk(Request_id, Payload) ->
    Size = erlang:iolist_size(Payload),
    gleam@bool:guard(
        Size =:= 0,
        gleam@bytes_tree:new(),
        fun() -> case Size =< 65535 of
                true ->
                    fcgi@internal@protocol:encode_record_tree_unchecked(
                        6,
                        Request_id,
                        Payload
                    );

                false ->
                    encode_records(
                        fcgi@internal@protocol:chunk_stdout(
                            Request_id,
                            erlang:list_to_bitstring(Payload)
                        )
                    )
            end end
    ).

-file("src/fcgi/internal/responder.gleam", 451).
?DOC(false).
-spec encode_response_headers(integer(), gleam@http@response:response(any())) -> gleam@bytes_tree:bytes_tree().
encode_response_headers(Request_id, Resp) ->
    encode_stdout_chunk(Request_id, encode_http_headers(Resp)).

-file("src/fcgi/internal/responder.gleam", 458).
?DOC(false).
-spec encode_response_end_records(integer()) -> gleam@bytes_tree:bytes_tree().
encode_response_end_records(Request_id) ->
    Stdout_end = {stdout, Request_id, <<>>},
    Request_end = {end_request, Request_id, 0, request_complete},
    encode_records([Stdout_end, Request_end]).