Skip to main content

src/internal@encoder@quoted_printable.erl

-module(internal@encoder@quoted_printable).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/internal/encoder/quoted_printable.gleam").
-export([estimate_encoded_size/2, encode_string/4]).

-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(
    " Encodes quoted-printable strings according to RFC 2045\n"
    " and RFC 2047.\n"
    "\n"
    " See the following links for reference:\n"
    " - <https://tools.ietf.org/html/rfc2045#section-6.7>\n"
    " - <https://tools.ietf.org/html/rfc2047>\n"
).

-file("src/internal/encoder/quoted_printable.gleam", 91).
-spec should_encode_character(binary(), internal@encoder@encoding:rfc()) -> boolean().
should_encode_character(Character, Rfc) ->
    (Character /= <<"\t"/utf8>>) andalso ((((gleam@string:compare(
        Character,
        <<"\x{20}"/utf8>>
    )
    =:= lt)
    orelse (gleam@string:compare(Character, <<"\x{7f}"/utf8>>) =:= gt))
    orelse (gleam@string:compare(Character, <<"="/utf8>>) =:= eq))
    orelse ((Rfc =:= rfc2047) andalso gleam_stdlib:contains_string(
        <<"()<>@,;:\"/[]?._"/utf8>>,
        Character
    ))).

-file("src/internal/encoder/quoted_printable.gleam", 19).
?DOC(
    " Estimates the quoted printable encoded size of a string in bytes,\n"
    " taking the desired RFC constraints into account.\n"
).
-spec estimate_encoded_size(binary(), internal@encoder@encoding:rfc()) -> {ok,
        integer()} |
    {error, internal@encoder@encoding:encoder_error()}.
estimate_encoded_size(String, Rfc) ->
    _pipe = String,
    _pipe@1 = gleam@string:split(_pipe, <<""/utf8>>),
    _pipe@2 = gleam@list:fold(
        _pipe@1,
        0,
        fun(Size, Character) ->
            Character_size = erlang:byte_size(Character),
            case should_encode_character(Character, Rfc) of
                true ->
                    Size + (3 * Character_size);

                false ->
                    Size + Character_size
            end
        end
    ),
    {ok, _pipe@2}.

-file("src/internal/encoder/quoted_printable.gleam", 117).
-spec do_encode_bytes(bitstring(), list(binary())) -> binary().
do_encode_bytes(Bytes, Accumulator) ->
    case Bytes of
        <<Byte:1/binary, Other_bytes/binary>> ->
            do_encode_bytes(
                Other_bytes,
                [<<"="/utf8, (gleam_stdlib:base16_encode(Byte))/binary>> |
                    Accumulator]
            );

        <<_/bitstring>> ->
            _pipe = Accumulator,
            _pipe@1 = lists:reverse(_pipe),
            gleam@string:join(_pipe@1, <<""/utf8>>)
    end.

-file("src/internal/encoder/quoted_printable.gleam", 106).
-spec encode_character(binary(), internal@encoder@encoding:rfc(), boolean()) -> binary().
encode_character(Character, Rfc, Force_encoding) ->
    Should_encode = should_encode_character(Character, Rfc) orelse Force_encoding,
    gleam@bool:guard(
        not Should_encode,
        Character,
        fun() -> do_encode_bytes(gleam_stdlib:identity(Character), []) end
    ).

-file("src/internal/encoder/quoted_printable.gleam", 101).
-spec is_whitespace(binary()) -> boolean().
is_whitespace(Character) ->
    (gleam@string:compare(Character, <<"\t"/utf8>>) =:= eq) orelse (gleam@string:compare(
        Character,
        <<" "/utf8>>
    )
    =:= eq).

-file("src/internal/encoder/quoted_printable.gleam", 51).
-spec do_encode_string(
    list(binary()),
    internal@encoder@encoding:rfc(),
    integer(),
    {integer(), list(binary())}
) -> {ok, list(binary())} | {error, internal@encoder@encoding:encoder_error()}.
do_encode_string(Characters, Rfc, Preferred_size, Accumulator) ->
    {Used_size, Lines} = Accumulator,
    case Characters of
        [] ->
            {ok, Lines};

        [Character | Other_characters] ->
            Force_encoding = is_whitespace(Character) andalso gleam@list:is_empty(
                Other_characters
            ),
            Encoded_character = encode_character(Character, Rfc, Force_encoding),
            Encoded_character_size = erlang:byte_size(Encoded_character),
            _pipe = case Lines of
                [] ->
                    {Encoded_character_size, [Encoded_character]};

                [First_line | Other_lines] when ((Used_size + Encoded_character_size) < Preferred_size) orelse ((Other_characters =:= []) andalso ((Used_size + Encoded_character_size) =:= Preferred_size)) ->
                    {Used_size + Encoded_character_size,
                        [<<First_line/binary, Encoded_character/binary>> |
                            Other_lines]};

                [First_line@1 | Other_lines@1] ->
                    {Encoded_character_size,
                        [Encoded_character,
                            <<First_line@1/binary, "="/utf8>> |
                            Other_lines@1]}
            end,
            do_encode_string(Other_characters, Rfc, Preferred_size, _pipe)
    end.

-file("src/internal/encoder/quoted_printable.gleam", 39).
?DOC(
    " Quoted printable encode a string, taking the desired RFC\n"
    " constraints and the preferred line size into account, and\n"
    " pretending to start the first line at the passed start position.\n"
).
-spec encode_string(
    binary(),
    internal@encoder@encoding:rfc(),
    integer(),
    integer()
) -> {ok, list(binary())} | {error, internal@encoder@encoding:encoder_error()}.
encode_string(String, Rfc, Position, Preferred_size) ->
    _pipe = String,
    _pipe@1 = gleam@string:split(_pipe, <<""/utf8>>),
    _pipe@2 = do_encode_string(_pipe@1, Rfc, Preferred_size, {Position, []}),
    gleam@result:map(_pipe@2, fun lists:reverse/1).