-module(smtp). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([send/4]). -export_type([error/0]). -type error() :: {tcp_error, mug:error()} | {invalid_server_code_error, binary(), integer()}. -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 16). -spec tcp_connect(binary(), integer(), integer()) -> {ok, mug:socket()} | {error, error()}. tcp_connect(Host, Port, Timeout) -> _pipe = mug:new(Host, Port), _pipe@1 = mug:timeout(_pipe, Timeout), _pipe@2 = mug:connect(_pipe@1), gleam@result:map_error(_pipe@2, fun(Field@0) -> {tcp_error, Field@0} end). -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 23). -spec tcp_send(mug:socket(), binary()) -> {ok, nil} | {error, error()}. tcp_send(Socket, Message) -> logging:log(debug, <<"CLIENT: "/utf8, Message/binary>>), _pipe = mug:send(Socket, <>), gleam@result:map_error(_pipe, fun(Field@0) -> {tcp_error, Field@0} end). -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 30). -spec tcp_receive(mug:socket(), integer()) -> {ok, bitstring()} | {error, error()}. tcp_receive(Socket, Timeout) -> gleam@result:'try'( begin _pipe = mug:'receive'(Socket, Timeout), gleam@result:map_error( _pipe, fun(Field@0) -> {tcp_error, Field@0} end ) end, fun(Packet) -> _assert_subject = gleam@bit_array:to_string(Packet), {ok, Packet_str} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail, module => <<"smtp"/utf8>>, function => <<"tcp_receive"/utf8>>, line => 36}) end, logging:log(debug, <<"SERVER: "/utf8, Packet_str/binary>>), {ok, Packet} end ). -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 42). -spec check_server_code(integer(), bitstring()) -> {ok, nil} | {error, error()}. check_server_code(Expected_code, Response) -> _assert_subject = gleam@bit_array:to_string(Response), {ok, Response_str} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail, module => <<"smtp"/utf8>>, function => <<"check_server_code"/utf8>>, line => 43}) end, Server_code = gleam@string:slice(Response_str, 0, 3), gleam@result:'try'( begin _pipe = gleam_stdlib:parse_int(Server_code), gleam@result:map_error( _pipe, fun(_) -> {invalid_server_code_error, Server_code, Expected_code} end ) end, fun(Parsed_code) -> case Parsed_code =:= Expected_code of true -> {ok, nil}; false -> {error, {invalid_server_code_error, Server_code, Expected_code}} end end ). -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 60). -spec cmd(mug:socket(), integer(), binary()) -> {ok, bitstring()} | {error, error()}. cmd(Socket, Expected_code, Message) -> gleam@result:'try'( tcp_send(Socket, <>), fun(_) -> gleam@result:'try'( tcp_receive(Socket, 500), fun(Packet) -> gleam@result:'try'( check_server_code(Expected_code, Packet), fun(_) -> {ok, Packet} end ) end ) end ). -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 70). -spec opening_message(mug:socket(), integer()) -> {ok, nil} | {error, error()}. opening_message(Socket, Timeout) -> gleam@result:'try'( tcp_receive(Socket, Timeout), fun(Packet) -> check_server_code(220, Packet) end ). -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 75). -spec find_extension(bitstring()) -> {ok, list(binary())} | {error, any()}. find_extension(Response) -> Try_split_once = fun(Extension, Delimiter) -> _pipe = gleam@string:split_once(Extension, Delimiter), gleam@result:map( _pipe, fun(Extension@1) -> erlang:element(2, Extension@1) end ) end, _assert_subject = gleam@bit_array:to_string(Response), {ok, Response_str} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail, module => <<"smtp"/utf8>>, function => <<"find_extension"/utf8>>, line => 81}) end, Extensions = begin _pipe@1 = gleam@string:split(Response_str, <<"\n"/utf8>>), _pipe@2 = gleam@list:drop(_pipe@1, 1), _pipe@4 = gleam@list:map( _pipe@2, fun(Extension@2) -> _pipe@3 = Try_split_once(Extension@2, <<"-"/utf8>>), gleam@result:'or'( _pipe@3, Try_split_once(Extension@2, <<" "/utf8>>) ) end ), gleam@list:filter_map(_pipe@4, fun(X) -> X end) end, {ok, Extensions}. -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 95). -spec has_extension(list(binary()), binary()) -> boolean(). has_extension(Extensions, Search) -> _pipe = gleam@list:find( Extensions, fun(Extension) -> gleam_stdlib:string_starts_with(Extension, Search) end ), gleam@result:is_ok(_pipe). -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 112). -spec helo(mug:socket(), binary()) -> {ok, bitstring()} | {error, error()}. helo(Socket, Host) -> cmd(Socket, 250, <<"HELO "/utf8, Host/binary>>). -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 100). -spec ehlo(mug:socket(), binary()) -> {ok, list(binary())} | {error, error()}. ehlo(Socket, Host) -> case cmd(Socket, 250, <<"EHLO "/utf8, Host/binary>>) of {ok, Response} -> find_extension(Response); {error, _} -> _pipe = helo(Socket, Host), gleam@result:map(_pipe, fun(_) -> [] end) end. -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 116). -spec auth_plain( mug:socket(), list(binary()), gleam@option:option({binary(), binary()}) ) -> {ok, bitstring()} | {error, error()}. auth_plain(Socket, Extensions, Auth) -> Has_auth_extension = has_extension(Extensions, <<"AUTH"/utf8>>), case {Has_auth_extension, Auth} of {true, {some, {User, Password}}} -> User_and_password = <<<<<<"\x{0000}"/utf8, User/binary>>/binary, "\x{0000}"/utf8>>/binary, Password/binary>>, Base64_encoded = gleam_stdlib:bit_array_base64_encode( <>, true ), cmd(Socket, 235, <<"AUTH PLAIN "/utf8, Base64_encoded/binary>>); {_, _} -> {ok, <<>>} end. -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 131). -spec mail_from(mug:socket(), binary()) -> {ok, bitstring()} | {error, error()}. mail_from(Socket, From) -> cmd(Socket, 250, <<<<"MAIL FROM:<"/utf8, From/binary>>/binary, ">"/utf8>>). -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 135). -spec rcpt_to(mug:socket(), list(binary())) -> {ok, bitstring()} | {error, error()}. rcpt_to(Socket, To) -> cmd( Socket, 250, <<<<"RCPT TO:<"/utf8, (gleam@string:join(To, <<","/utf8>>))/binary>>/binary, ">"/utf8>> ). -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 139). -spec data(mug:socket()) -> {ok, bitstring()} | {error, error()}. data(Socket) -> cmd(Socket, 354, <<"DATA"/utf8>>). -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 143). -spec data_body(mug:socket(), builder:message()) -> {ok, bitstring()} | {error, error()}. data_body(Socket, Message) -> Body = begin _pipe = <<""/utf8>>, _pipe@1 = gleam@string:append( _pipe, <<<<"Subject: "/utf8, (erlang:element(4, Message))/binary>>/binary, "\r\n"/utf8>> ), _pipe@2 = gleam@string:append( _pipe@1, <<<<"From: "/utf8, (erlang:element(2, Message))/binary>>/binary, "\r\n"/utf8>> ), _pipe@3 = gleam@string:append( _pipe@2, <<<<"To: "/utf8, (gleam@string:join(erlang:element(3, Message), <<","/utf8>>))/binary>>/binary, "\r\n"/utf8>> ), _pipe@4 = gleam@string:append(_pipe@3, <<"\r\n"/utf8>>), _pipe@5 = gleam@string:append(_pipe@4, erlang:element(5, Message)), gleam@string:append(_pipe@5, <<"\r\n."/utf8>>) end, cmd(Socket, 250, Body). -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 156). -spec quit(mug:socket()) -> {ok, bitstring()} | {error, error()}. quit(Socket) -> cmd(Socket, 221, <<"QUIT"/utf8>>). -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 160). -spec log_message(binary(), integer(), builder:message()) -> nil. log_message(Host, Port, Message) -> logging:log(debug, <<"SENDING MAIL"/utf8>>), logging:log( debug, <<<<<<"HOST: "/utf8, Host/binary>>/binary, ":"/utf8>>/binary, (begin _pipe = Port, erlang:integer_to_binary(_pipe) end)/binary>> ), logging:log(debug, <<"FROM: "/utf8, (erlang:element(2, Message))/binary>>), logging:log( debug, <<"TO: "/utf8, (gleam@string:join(erlang:element(3, Message), <<", "/utf8>>))/binary>> ), logging:log( debug, <<"SUBJECT: "/utf8, (erlang:element(4, Message))/binary>> ), logging:log(debug, <<"BODY: "/utf8, (erlang:element(5, Message))/binary>>). -file("/home/andre/Code/gleam/gsmtp/src/smtp.gleam", 169). -spec send( binary(), integer(), gleam@option:option({binary(), binary()}), builder:message() ) -> {ok, nil} | {error, error()}. send(Host, Port, Auth, Message) -> log_message(Host, Port, Message), Timeout = 500, gleam@result:'try'( tcp_connect(Host, Port, Timeout), fun(Socket) -> gleam@result:'try'( opening_message(Socket, Timeout), fun(_) -> gleam@result:'try'( ehlo(Socket, <<"localhost"/utf8>>), fun(Extensions) -> gleam@result:'try'( auth_plain(Socket, Extensions, Auth), fun(_) -> gleam@result:'try'( mail_from( Socket, erlang:element(2, Message) ), fun(_) -> gleam@result:'try'( rcpt_to( Socket, erlang:element(3, Message) ), fun(_) -> gleam@result:'try'( data(Socket), fun(_) -> gleam@result:'try'( data_body( Socket, Message ), fun(_) -> gleam@result:'try'( quit( Socket ), fun(_) -> {ok, nil} end ) end ) end ) end ) end ) end ) end ) end ) end ).