mirror of
https://github.com/internetee/epp_proxy.git
synced 2025-08-15 20:13:47 +02:00
Add rebar3_fmt format tool
This commit is contained in:
parent
01b9aa9912
commit
2e01b6870b
19 changed files with 415 additions and 372 deletions
|
@ -1 +1,2 @@
|
||||||
erlang 21.3.8
|
erlang 21.3.8
|
||||||
|
rebar 3.9.1
|
||||||
|
|
|
@ -9,6 +9,14 @@ emulate it's behaviour to the biggest possible extent.
|
||||||
Aside from the standard library of Erlang/OTP, it uses hackney for making HTTP requests
|
Aside from the standard library of Erlang/OTP, it uses hackney for making HTTP requests
|
||||||
and lager for logging.
|
and lager for logging.
|
||||||
|
|
||||||
|
Code style
|
||||||
|
----
|
||||||
|
We enforce the style from Erlang's own configuration.
|
||||||
|
You can use the rebar3_fmt plugin to do it for you:
|
||||||
|
|
||||||
|
$ rebar3 fmt
|
||||||
|
|
||||||
|
|
||||||
Design
|
Design
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
|
|
||||||
-include_lib("public_key/include/public_key.hrl").
|
-include_lib("public_key/include/public_key.hrl").
|
||||||
|
|
||||||
-export([pem_certificate/1, subject_from_otp_certificate/1,
|
-export([certificate_to_pem/1,
|
||||||
common_name_from_subject/1, certificate_to_pem/1,
|
common_name_from_subject/1, der_certificate/1,
|
||||||
der_certificate/1, headers_from_cert/1]).
|
headers_from_cert/1, pem_certificate/1,
|
||||||
|
subject_from_otp_certificate/1]).
|
||||||
|
|
||||||
% Returns a tuple of headers {SSL_CLIENT_S_DN_CN, SSL_CLIENT_CERT}
|
% Returns a tuple of headers {SSL_CLIENT_S_DN_CN, SSL_CLIENT_CERT}
|
||||||
headers_from_cert(Der) ->
|
headers_from_cert(Der) ->
|
||||||
|
@ -24,26 +25,31 @@ der_certificate(Der) ->
|
||||||
certificate_to_pem(Certificate) ->
|
certificate_to_pem(Certificate) ->
|
||||||
PemEntry = {'Certificate', Certificate, not_encrypted},
|
PemEntry = {'Certificate', Certificate, not_encrypted},
|
||||||
PemString = public_key:pem_encode([PemEntry]),
|
PemString = public_key:pem_encode([PemEntry]),
|
||||||
CleanBinary = binary:replace(PemString, <<"\n">>, <<" ">>, [global]),
|
CleanBinary = binary:replace(PemString, <<"\n">>,
|
||||||
|
<<" ">>, [global]),
|
||||||
CleanBinary.
|
CleanBinary.
|
||||||
|
|
||||||
%% Read only a specific type of certificate, otherwise fail.
|
%% Read only a specific type of certificate, otherwise fail.
|
||||||
subject_from_otp_certificate(Certificate) when is_record(Certificate, 'OTPCertificate') ->
|
subject_from_otp_certificate(Certificate)
|
||||||
Subject = Certificate#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.subject,
|
when is_record(Certificate, 'OTPCertificate') ->
|
||||||
|
Subject =
|
||||||
|
(Certificate#'OTPCertificate'.tbsCertificate)#'OTPTBSCertificate'.subject,
|
||||||
Subject.
|
Subject.
|
||||||
|
|
||||||
%% Take a subject rdnSequence that can be set into
|
%% Take a subject rdnSequence that can be set into
|
||||||
%% HTTP header SSL_CLIENT_S_DN_CN.
|
%% HTTP header SSL_CLIENT_S_DN_CN.
|
||||||
common_name_from_subject(Subject) ->
|
common_name_from_subject(Subject) ->
|
||||||
CommonName = ?'id-at-commonName',
|
CommonName = (?'id-at-commonName'),
|
||||||
{_Type, Field} = field_from_subject(Subject, CommonName),
|
{_Type, Field} = field_from_subject(Subject,
|
||||||
|
CommonName),
|
||||||
Field.
|
Field.
|
||||||
|
|
||||||
%% Only used for local development test, is not required for the application.
|
%% Only used for local development test, is not required for the application.
|
||||||
pem_certificate(PathToCert) ->
|
pem_certificate(PathToCert) ->
|
||||||
{ok, PemBin} = file:file(PathToCert),
|
{ok, PemBin} = file:file(PathToCert),
|
||||||
PemEntries = public_key:pem_decode(PemBin),
|
PemEntries = public_key:pem_decode(PemBin),
|
||||||
{value, CertEntry} = lists:keysearch('Certificate', 1, PemEntries),
|
{value, CertEntry} = lists:keysearch('Certificate', 1,
|
||||||
|
PemEntries),
|
||||||
{_, DerCert, _} = CertEntry,
|
{_, DerCert, _} = CertEntry,
|
||||||
Decoded = public_key:pkix_decode_cert(DerCert, otp),
|
Decoded = public_key:pkix_decode_cert(DerCert, otp),
|
||||||
Decoded.
|
Decoded.
|
||||||
|
@ -52,6 +58,7 @@ field_from_subject({rdnSequence, Attributes}, Field) ->
|
||||||
FlatList = lists:flatten(Attributes),
|
FlatList = lists:flatten(Attributes),
|
||||||
ValidAttrs = lists:filter(fun (X) ->
|
ValidAttrs = lists:filter(fun (X) ->
|
||||||
X#'AttributeTypeAndValue'.type =:= Field
|
X#'AttributeTypeAndValue'.type =:= Field
|
||||||
end, FlatList),
|
end,
|
||||||
|
FlatList),
|
||||||
Attribute = lists:last(ValidAttrs),
|
Attribute = lists:last(ValidAttrs),
|
||||||
Attribute#'AttributeTypeAndValue'.value.
|
Attribute#'AttributeTypeAndValue'.value.
|
||||||
|
|
|
@ -4,126 +4,130 @@
|
||||||
|
|
||||||
-behaviour(epp_http_client_behaviour).
|
-behaviour(epp_http_client_behaviour).
|
||||||
|
|
||||||
-export([request/1, error_request/1, request_builder/1]).
|
-export([error_request/1, request/1,
|
||||||
|
request_builder/1]).
|
||||||
|
|
||||||
%% Callback API
|
%% Callback API
|
||||||
request(#epp_request{} = Request) ->
|
request(#epp_request{} = Request) ->
|
||||||
HackneyArgs = handle_args(Request),
|
HackneyArgs = handle_args(Request),
|
||||||
case apply(hackney, request, HackneyArgs) of
|
case apply(hackney, request, HackneyArgs) of
|
||||||
{error, Error} ->
|
{error, Error} -> log_and_return_canned(Error, Request);
|
||||||
log_and_return_canned(Error, Request);
|
|
||||||
{Status, _StatusCode, _Headers, ClientRef} ->
|
{Status, _StatusCode, _Headers, ClientRef} ->
|
||||||
{ok, Body} = hackney:body(ClientRef),
|
{ok, Body} = hackney:body(ClientRef), {Status, Body}
|
||||||
{Status, Body}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
error_request(#epp_error_request{} = Request) ->
|
error_request(#epp_error_request{} = Request) ->
|
||||||
HackneyArgs = handle_error_args(Request),
|
HackneyArgs = handle_error_args(Request),
|
||||||
case apply(hackney, request, HackneyArgs) of
|
case apply(hackney, request, HackneyArgs) of
|
||||||
{error, Error} ->
|
{error, Error} -> log_and_return_canned(Error, Request);
|
||||||
log_and_return_canned(Error, Request);
|
|
||||||
{Status, _StatusCode, _Headers, ClientRef} ->
|
{Status, _StatusCode, _Headers, ClientRef} ->
|
||||||
{ok, Body} = hackney:body(ClientRef),
|
{ok, Body} = hackney:body(ClientRef), {Status, Body}
|
||||||
{Status, Body}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
request_builder(Map) ->
|
request_builder(Map) -> request_from_map(Map).
|
||||||
request_from_map(Map).
|
|
||||||
|
|
||||||
%% Private API
|
%% Private API
|
||||||
-spec handle_args(epp_request()) -> list().
|
-spec handle_args(epp_request()) -> list().
|
||||||
handle_args(#epp_request{method=get, url=URL, headers=Headers, body="",
|
|
||||||
cookies=Cookies}) ->
|
handle_args(#epp_request{method = get, url = URL,
|
||||||
|
headers = Headers, body = "", cookies = Cookies}) ->
|
||||||
[get, URL, Headers, "", [{cookie, Cookies}, insecure]];
|
[get, URL, Headers, "", [{cookie, Cookies}, insecure]];
|
||||||
handle_args(#epp_request{method=post, url=URL, headers=Headers, body=Body,
|
handle_args(#epp_request{method = post, url = URL,
|
||||||
cookies=Cookies}) ->
|
headers = Headers, body = Body, cookies = Cookies}) ->
|
||||||
[post, URL, Headers, Body, [{cookie, Cookies}, insecure]].
|
[post, URL, Headers, Body,
|
||||||
|
[{cookie, Cookies}, insecure]].
|
||||||
|
|
||||||
-spec handle_error_args(epp_error_request()) -> list().
|
-spec handle_error_args(epp_error_request()) -> list().
|
||||||
handle_error_args(#epp_error_request{method=get, url=URL, headers=Headers,
|
|
||||||
query_params=Params, cookies=Cookies}) ->
|
handle_error_args(#epp_error_request{method = get,
|
||||||
|
url = URL, headers = Headers,
|
||||||
|
query_params = Params,
|
||||||
|
cookies = Cookies}) ->
|
||||||
QueryString = hackney_url:qs(Params),
|
QueryString = hackney_url:qs(Params),
|
||||||
CompleteURL = [URL, <<"?">>, QueryString],
|
CompleteURL = [URL, <<"?">>, QueryString],
|
||||||
[get, CompleteURL, Headers, "", [{cookie, Cookies}, insecure]].
|
[get, CompleteURL, Headers, "",
|
||||||
|
[{cookie, Cookies}, insecure]].
|
||||||
|
|
||||||
%% Map request and return values.
|
%% Map request and return values.
|
||||||
request_from_map(#{command := "error", session_id := SessionId,
|
request_from_map(#{command := "error",
|
||||||
code := Code, message := Message, headers:=Headers,
|
session_id := SessionId, code := Code,
|
||||||
|
message := Message, headers := Headers,
|
||||||
cl_trid := ClTRID}) ->
|
cl_trid := ClTRID}) ->
|
||||||
URL = epp_router:route_request("error"),
|
URL = epp_router:route_request("error"),
|
||||||
RequestMethod = epp_router:request_method("error"),
|
RequestMethod = epp_router:request_method("error"),
|
||||||
Cookie = hackney_cookie:setcookie("session", SessionId, []),
|
Cookie = hackney_cookie:setcookie("session", SessionId,
|
||||||
|
[]),
|
||||||
QueryParams = query_params(Code, Message, ClTRID),
|
QueryParams = query_params(Code, Message, ClTRID),
|
||||||
Headers = Headers,
|
Headers = Headers,
|
||||||
Request = #epp_error_request{url = URL,
|
Request = #epp_error_request{url = URL,
|
||||||
method = RequestMethod,
|
method = RequestMethod,
|
||||||
query_params=QueryParams,
|
query_params = QueryParams, cookies = [Cookie],
|
||||||
cookies=[Cookie],
|
|
||||||
headers = Headers},
|
headers = Headers},
|
||||||
lager:info("Error Request from map: [~p]~n", [Request]),
|
lager:info("Error Request from map: [~p]~n", [Request]),
|
||||||
Request;
|
Request;
|
||||||
request_from_map(#{command := Command, session_id := SessionId,
|
request_from_map(#{command := Command,
|
||||||
raw_frame := RawFrame, headers:=Headers, cl_trid := ClTRID}) ->
|
session_id := SessionId, raw_frame := RawFrame,
|
||||||
|
headers := Headers, cl_trid := ClTRID}) ->
|
||||||
URL = epp_router:route_request(Command),
|
URL = epp_router:route_request(Command),
|
||||||
RequestMethod = epp_router:request_method(Command),
|
RequestMethod = epp_router:request_method(Command),
|
||||||
Cookie = hackney_cookie:setcookie("session", SessionId, []),
|
Cookie = hackney_cookie:setcookie("session", SessionId,
|
||||||
|
[]),
|
||||||
Body = request_body(Command, RawFrame, ClTRID),
|
Body = request_body(Command, RawFrame, ClTRID),
|
||||||
Headers = Headers,
|
Headers = Headers,
|
||||||
Request = #epp_request{url = URL,
|
Request = #epp_request{url = URL,
|
||||||
method=RequestMethod,
|
method = RequestMethod, body = Body,
|
||||||
body=Body,
|
cookies = [Cookie], headers = Headers},
|
||||||
cookies=[Cookie],
|
|
||||||
headers=Headers},
|
|
||||||
lager:info("Request from map: [~p]~n", [Request]),
|
lager:info("Request from map: [~p]~n", [Request]),
|
||||||
Request;
|
Request;
|
||||||
request_from_map(#{command := Command, session_id := SessionId,
|
request_from_map(#{command := Command,
|
||||||
raw_frame := RawFrame, common_name := CommonName,
|
session_id := SessionId, raw_frame := RawFrame,
|
||||||
client_cert := ClientCert, peer_ip := PeerIp, cl_trid := ClTRID}) ->
|
common_name := CommonName, client_cert := ClientCert,
|
||||||
|
peer_ip := PeerIp, cl_trid := ClTRID}) ->
|
||||||
URL = epp_router:route_request(Command),
|
URL = epp_router:route_request(Command),
|
||||||
RequestMethod = epp_router:request_method(Command),
|
RequestMethod = epp_router:request_method(Command),
|
||||||
Cookie = hackney_cookie:setcookie("session", SessionId, []),
|
Cookie = hackney_cookie:setcookie("session", SessionId,
|
||||||
|
[]),
|
||||||
Body = request_body(Command, RawFrame, ClTRID),
|
Body = request_body(Command, RawFrame, ClTRID),
|
||||||
Headers = [{"SSL_CLIENT_CERT", ClientCert},
|
Headers = [{"SSL_CLIENT_CERT", ClientCert},
|
||||||
{"SSL_CLIENT_S_DN_CN", CommonName},
|
{"SSL_CLIENT_S_DN_CN", CommonName},
|
||||||
{"User-Agent", <<"EPP proxy">>},
|
{"User-Agent", <<"EPP proxy">>},
|
||||||
{"X-Forwarded-for", epp_util:readable_ip(PeerIp)}],
|
{"X-Forwarded-for", epp_util:readable_ip(PeerIp)}],
|
||||||
Request = #epp_request{url = URL,
|
Request = #epp_request{url = URL,
|
||||||
method=RequestMethod,
|
method = RequestMethod, body = Body,
|
||||||
body=Body,
|
cookies = [Cookie], headers = Headers},
|
||||||
cookies=[Cookie],
|
|
||||||
headers=Headers},
|
|
||||||
lager:info("Request from map: [~p]~n", [Request]),
|
lager:info("Request from map: [~p]~n", [Request]),
|
||||||
Request.
|
Request.
|
||||||
|
|
||||||
%% Return form data or an empty list.
|
%% Return form data or an empty list.
|
||||||
request_body("hello", _, _) ->
|
request_body("hello", _, _) -> "";
|
||||||
"";
|
|
||||||
request_body(_Command, RawFrame, nomatch) ->
|
request_body(_Command, RawFrame, nomatch) ->
|
||||||
{multipart, [{<<"raw_frame">>, RawFrame}]};
|
{multipart, [{<<"raw_frame">>, RawFrame}]};
|
||||||
request_body(_Command, RawFrame, ClTRID) ->
|
request_body(_Command, RawFrame, ClTRID) ->
|
||||||
{multipart, [{<<"raw_frame">>, RawFrame}, {<<"clTRID">>, ClTRID}]}.
|
{multipart,
|
||||||
|
[{<<"raw_frame">>, RawFrame}, {<<"clTRID">>, ClTRID}]}.
|
||||||
|
|
||||||
query_params(Code, Message, nomatch) ->
|
query_params(Code, Message, nomatch) ->
|
||||||
[{<<"code">>, Code}, {<<"msg">>, Message}];
|
[{<<"code">>, Code}, {<<"msg">>, Message}];
|
||||||
query_params(Code, Message, ClTRID) ->
|
query_params(Code, Message, ClTRID) ->
|
||||||
[{<<"code">>, Code}, {<<"msg">>, Message}, {<<"clTRID">>, ClTRID}].
|
[{<<"code">>, Code}, {<<"msg">>, Message},
|
||||||
|
{<<"clTRID">>, ClTRID}].
|
||||||
|
|
||||||
%% Log critical information about a request that failed, and then
|
%% Log critical information about a request that failed, and then
|
||||||
%% return a canned response with internal error status.
|
%% return a canned response with internal error status.
|
||||||
log_and_return_canned(Error, Request) ->
|
log_and_return_canned(Error, Request) ->
|
||||||
lager:alert("Registry cannot be reached!"),
|
lager:alert("Registry cannot be reached!"),
|
||||||
lager:alert("Error contacting registry: [~p, ~p]~n", [Error, Request]),
|
lager:alert("Error contacting registry: [~p, ~p]~n",
|
||||||
|
[Error, Request]),
|
||||||
{2400, canned_response()}.
|
{2400, canned_response()}.
|
||||||
|
|
||||||
%% In case registry is not accessible, return this response.
|
%% In case registry is not accessible, return this response.
|
||||||
%% In the future, this should be configurable.
|
%% In the future, this should be configurable.
|
||||||
canned_response() ->
|
canned_response() ->
|
||||||
<<"<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
<<"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<"
|
||||||
<epp xmlns=\"https://epp.tld.ee/schema/epp-ee-1.0.xsd\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"lib/schemas/epp-ee-1.0.xsd\">
|
"epp xmlns=\"https://epp.tld.ee/schema/epp-ee-"
|
||||||
<response>
|
"1.0.xsd\" xmlns:xsi=\"http://www.w3.org/2001/"
|
||||||
<result code=\"2400\">
|
"XMLSchema-instance\" xsi:schemaLocation=\"lib"
|
||||||
<msg lang=\"en\">Internal server error.</msg>
|
"/schemas/epp-ee-1.0.xsd\">\n <response>\n "
|
||||||
</result>
|
" <result code=\"2400\">\n <msg "
|
||||||
</response>
|
"lang=\"en\">Internal server error.</msg>\n "
|
||||||
</epp>">>.
|
" </result>\n </response>\n</epp>">>.
|
||||||
|
|
|
@ -7,5 +7,9 @@
|
||||||
%% Abstract module for http client behaviour. It should call EPP HTTP server
|
%% Abstract module for http client behaviour. It should call EPP HTTP server
|
||||||
%% and return a response back to the caller.
|
%% and return a response back to the caller.
|
||||||
-callback request(epp_request()) -> http_response().
|
-callback request(epp_request()) -> http_response().
|
||||||
-callback error_request(epp_error_request()) -> http_response().
|
|
||||||
-callback request_builder(map()) -> epp_request() | epp_error_request().
|
-callback
|
||||||
|
error_request(epp_error_request()) -> http_response().
|
||||||
|
|
||||||
|
-callback request_builder(map()) -> epp_request() |
|
||||||
|
epp_error_request().
|
||||||
|
|
|
@ -12,5 +12,6 @@ start_link() ->
|
||||||
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
|
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
SupFlags = #{strategy => one_for_one, intensity => 3, period => 60},
|
SupFlags = #{strategy => one_for_one, intensity => 3,
|
||||||
|
period => 60},
|
||||||
{ok, {SupFlags, []}}.
|
{ok, {SupFlags, []}}.
|
||||||
|
|
|
@ -18,9 +18,9 @@ start(_StartType, _StartArgs) ->
|
||||||
epp_proxy_sup:start_link().
|
epp_proxy_sup:start_link().
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
stop(_State) ->
|
stop(_State) -> ok.
|
||||||
ok.
|
|
||||||
|
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,10 @@
|
||||||
-export([init/1]).
|
-export([init/1]).
|
||||||
|
|
||||||
-define(SERVER, ?MODULE).
|
-define(SERVER, ?MODULE).
|
||||||
-define(DevMode, application:get_env(epp_proxy, dev_mode)).
|
|
||||||
|
-define(DevMode,
|
||||||
|
application:get_env(epp_proxy, dev_mode)).
|
||||||
|
|
||||||
-define(TCPPort,
|
-define(TCPPort,
|
||||||
case application:get_env(epp_proxy, tcp_port) of
|
case application:get_env(epp_proxy, tcp_port) of
|
||||||
undefined -> undefined;
|
undefined -> undefined;
|
||||||
|
@ -27,7 +30,6 @@
|
||||||
{ok, Val} -> Val
|
{ok, Val} -> Val
|
||||||
end).
|
end).
|
||||||
|
|
||||||
|
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
%% API functions
|
%% API functions
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
|
@ -44,21 +46,20 @@ start_link() ->
|
||||||
%% Before OTP 18 tuples must be used to specify a child. e.g.
|
%% Before OTP 18 tuples must be used to specify a child. e.g.
|
||||||
%% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules}
|
%% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules}
|
||||||
init([]) ->
|
init([]) ->
|
||||||
SupFlags = #{strategy => one_for_one, intensity => 3, period => 60},
|
SupFlags = #{strategy => one_for_one, intensity => 3,
|
||||||
TCPAcceptor = #{id => epp_tcp_acceptor,
|
period => 60},
|
||||||
type => worker,
|
TCPAcceptor = #{id => epp_tcp_acceptor, type => worker,
|
||||||
modules => [epp_tcp_acceptor],
|
modules => [epp_tcp_acceptor],
|
||||||
start => {epp_tcp_acceptor, start_link, [?TCPPort]}},
|
start => {epp_tcp_acceptor, start_link, [?TCPPort]}},
|
||||||
TLSAcceptor = #{id => epp_tls_acceptor,
|
TLSAcceptor = #{id => epp_tls_acceptor, type => worker,
|
||||||
type => worker,
|
|
||||||
modules => [epp_tls_acceptor],
|
modules => [epp_tls_acceptor],
|
||||||
start => {epp_tls_acceptor, start_link, [?TLSPort]}},
|
start => {epp_tls_acceptor, start_link, [?TLSPort]}},
|
||||||
PoolSupervisor = #{id => epp_pool_supervisor,
|
PoolSupervisor = #{id => epp_pool_supervisor,
|
||||||
type => supervisor,
|
type => supervisor, modules => [epp_pool_supervisor],
|
||||||
modules => [epp_pool_supervisor],
|
|
||||||
start => {epp_pool_supervisor, start_link, []}},
|
start => {epp_pool_supervisor, start_link, []}},
|
||||||
ChildrenSpec = case ?DevMode of
|
ChildrenSpec = case ?DevMode of
|
||||||
{ok, true} -> [TCPAcceptor, TLSAcceptor, PoolSupervisor];
|
{ok, true} ->
|
||||||
|
[TCPAcceptor, TLSAcceptor, PoolSupervisor];
|
||||||
_ -> [TLSAcceptor, PoolSupervisor]
|
_ -> [TLSAcceptor, PoolSupervisor]
|
||||||
end,
|
end,
|
||||||
{ok, {SupFlags, ChildrenSpec}}.
|
{ok, {SupFlags, ChildrenSpec}}.
|
||||||
|
@ -66,3 +67,4 @@ init([]) ->
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
|
|
||||||
|
|
|
@ -1,52 +1,70 @@
|
||||||
-module(epp_router).
|
-module(epp_router).
|
||||||
|
|
||||||
-export([route_request/1, request_method/1]).
|
-export([request_method/1, route_request/1]).
|
||||||
|
|
||||||
-define(validCommands, ["hello", "login", "logout", "check", "info", "poll",
|
-define(validCommands,
|
||||||
|
["hello", "login", "logout", "check", "info", "poll",
|
||||||
"create", "delete", "renew", "update", "transfer"]).
|
"create", "delete", "renew", "update", "transfer"]).
|
||||||
|
|
||||||
%% 47 is the character code
|
%% 47 is the character code
|
||||||
-define(forwardSlash, 47).
|
-define(forwardSlash, 47).
|
||||||
|
|
||||||
%% request method: GET for greeting, POST for everything else.
|
%% request method: GET for greeting, POST for everything else.
|
||||||
request_method("hello") ->
|
request_method("hello") -> get;
|
||||||
get;
|
request_method(<<"hello">>) -> get;
|
||||||
request_method(<<"hello">>) ->
|
request_method("error") -> get;
|
||||||
get;
|
request_method(<<"error">>) -> get;
|
||||||
request_method("error") ->
|
request_method(_) -> post.
|
||||||
get;
|
|
||||||
request_method(<<"error">>) ->
|
|
||||||
get;
|
|
||||||
request_method(_) ->
|
|
||||||
post.
|
|
||||||
|
|
||||||
%% Base router
|
%% Base router
|
||||||
route_request(Command) when is_binary(Command) ->
|
route_request(Command) when is_binary(Command) ->
|
||||||
List = binary_to_list(Command),
|
List = binary_to_list(Command), url_map(List);
|
||||||
url_map(List);
|
route_request(Command) when is_list(Command) ->
|
||||||
route_request(Command) when is_list(Command) -> url_map(Command).
|
url_map(Command).
|
||||||
|
|
||||||
%% Actually route to places
|
%% Actually route to places
|
||||||
url_map(Command) when is_list(Command) ->
|
url_map(Command) when is_list(Command) ->
|
||||||
case Command of
|
case Command of
|
||||||
%% Session commands
|
%% Session commands
|
||||||
"hello" -> unicode:characters_to_list([base_session_url(), Command]);
|
"hello" ->
|
||||||
"login" -> unicode:characters_to_list([base_session_url(), Command]);
|
unicode:characters_to_list([base_session_url(),
|
||||||
"logout" -> unicode:characters_to_list([base_session_url(), Command]);
|
Command]);
|
||||||
|
"login" ->
|
||||||
|
unicode:characters_to_list([base_session_url(),
|
||||||
|
Command]);
|
||||||
|
"logout" ->
|
||||||
|
unicode:characters_to_list([base_session_url(),
|
||||||
|
Command]);
|
||||||
%% Poll commands
|
%% Poll commands
|
||||||
"check" -> unicode:characters_to_list([base_command_url(), Command]);
|
"check" ->
|
||||||
"info" -> unicode:characters_to_list([base_command_url(), Command]);
|
unicode:characters_to_list([base_command_url(),
|
||||||
"poll" -> unicode:characters_to_list([base_command_url(), Command]);
|
Command]);
|
||||||
|
"info" ->
|
||||||
|
unicode:characters_to_list([base_command_url(),
|
||||||
|
Command]);
|
||||||
|
"poll" ->
|
||||||
|
unicode:characters_to_list([base_command_url(),
|
||||||
|
Command]);
|
||||||
%% Transform commands
|
%% Transform commands
|
||||||
"create" -> unicode:characters_to_list([base_command_url(), Command]);
|
"create" ->
|
||||||
"delete" -> unicode:characters_to_list([base_command_url(), Command]);
|
unicode:characters_to_list([base_command_url(),
|
||||||
"renew" -> unicode:characters_to_list([base_command_url(), Command]);
|
Command]);
|
||||||
"update" -> unicode:characters_to_list([base_command_url(), Command]);
|
"delete" ->
|
||||||
|
unicode:characters_to_list([base_command_url(),
|
||||||
|
Command]);
|
||||||
|
"renew" ->
|
||||||
|
unicode:characters_to_list([base_command_url(),
|
||||||
|
Command]);
|
||||||
|
"update" ->
|
||||||
|
unicode:characters_to_list([base_command_url(),
|
||||||
|
Command]);
|
||||||
% Transfer is both poll and query
|
% Transfer is both poll and query
|
||||||
"transfer" -> unicode:characters_to_list([base_command_url(), Command]);
|
"transfer" ->
|
||||||
|
unicode:characters_to_list([base_command_url(),
|
||||||
|
Command]);
|
||||||
% Error route
|
% Error route
|
||||||
"error" -> base_error_url()
|
"error" ->
|
||||||
% Anything else should fail.
|
base_error_url() % Anything else should fail.
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% This allows the person who configures proxy to not care about trailing
|
%% This allows the person who configures proxy to not care about trailing
|
||||||
|
|
|
@ -6,45 +6,50 @@
|
||||||
-module(epp_tcp_acceptor).
|
-module(epp_tcp_acceptor).
|
||||||
|
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
-define(SERVER, ?MODULE).
|
-define(SERVER, ?MODULE).
|
||||||
|
|
||||||
-define(POOL_SUPERVISOR, epp_pool_supervisor).
|
-define(POOL_SUPERVISOR, epp_pool_supervisor).
|
||||||
|
|
||||||
-define(WORKER, epp_tcp_worker).
|
-define(WORKER, epp_tcp_worker).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
-export([init/1, handle_cast/2, handle_call/3, start_link/1]).
|
-export([handle_call/3, handle_cast/2, init/1,
|
||||||
|
start_link/1]).
|
||||||
|
|
||||||
-record(state, {socket, port, options}).
|
-record(state, {socket, port, options}).
|
||||||
|
|
||||||
start_link(Port) ->
|
start_link(Port) ->
|
||||||
gen_server:start_link({local, ?SERVER}, ?MODULE, Port, []).
|
gen_server:start_link({local, ?SERVER}, ?MODULE, Port,
|
||||||
|
[]).
|
||||||
|
|
||||||
init(Port) ->
|
init(Port) ->
|
||||||
Options = [binary,
|
Options = [binary, {packet, raw}, {active, false},
|
||||||
{packet, raw},
|
|
||||||
{active, false},
|
|
||||||
{reuseaddr, true}],
|
{reuseaddr, true}],
|
||||||
|
|
||||||
{ok, ListenSocket} = gen_tcp:listen(Port, Options),
|
{ok, ListenSocket} = gen_tcp:listen(Port, Options),
|
||||||
gen_server:cast(self(), accept),
|
gen_server:cast(self(), accept),
|
||||||
|
{ok,
|
||||||
{ok, #state{socket=ListenSocket, port=Port, options=Options}}.
|
#state{socket = ListenSocket, port = Port,
|
||||||
|
options = Options}}.
|
||||||
|
|
||||||
handle_cast(accept,
|
handle_cast(accept,
|
||||||
State = #state{socket=ListenSocket, port=Port, options=Options}) ->
|
State = #state{socket = ListenSocket, port = Port,
|
||||||
|
options = Options}) ->
|
||||||
{ok, AcceptSocket} = gen_tcp:accept(ListenSocket),
|
{ok, AcceptSocket} = gen_tcp:accept(ListenSocket),
|
||||||
{ok, NewOwner} = create_worker(AcceptSocket),
|
{ok, NewOwner} = create_worker(AcceptSocket),
|
||||||
ok = gen_tcp:controlling_process(AcceptSocket, NewOwner),
|
ok = gen_tcp:controlling_process(AcceptSocket,
|
||||||
|
NewOwner),
|
||||||
gen_server:cast(NewOwner, serve),
|
gen_server:cast(NewOwner, serve),
|
||||||
gen_server:cast(NewOwner, greeting),
|
gen_server:cast(NewOwner, greeting),
|
||||||
gen_server:cast(self(), accept),
|
gen_server:cast(self(), accept),
|
||||||
{noreply, State#state{socket=ListenSocket, port=Port, options=Options}}.
|
{noreply,
|
||||||
|
State#state{socket = ListenSocket, port = Port,
|
||||||
|
options = Options}}.
|
||||||
|
|
||||||
handle_call(_E, _From, State) -> {noreply, State}.
|
handle_call(_E, _From, State) -> {noreply, State}.
|
||||||
|
|
||||||
create_worker(Socket) ->
|
create_worker(Socket) ->
|
||||||
ChildSpec = #{id => rand:uniform(),
|
ChildSpec = #{id => rand:uniform(), type => worker,
|
||||||
type => worker,
|
modules => [?WORKER], restart => temporary,
|
||||||
modules => [?WORKER],
|
|
||||||
restart => temporary,
|
|
||||||
start => {?WORKER, start_link, [Socket]}},
|
start => {?WORKER, start_link, [Socket]}},
|
||||||
supervisor:start_child(?POOL_SUPERVISOR, ChildSpec).
|
supervisor:start_child(?POOL_SUPERVISOR, ChildSpec).
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
-module(epp_tcp_worker).
|
-module(epp_tcp_worker).
|
||||||
|
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
-define(SERVER, ?MODULE).
|
-define(SERVER, ?MODULE).
|
||||||
|
|
||||||
-include("epp_proxy.hrl").
|
-include("epp_proxy.hrl").
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
-export([init/1, handle_cast/2, handle_call/3, start_link/1]).
|
-export([handle_call/3, handle_cast/2, init/1,
|
||||||
|
start_link/1]).
|
||||||
|
|
||||||
-export([code_change/3]).
|
-export([code_change/3]).
|
||||||
|
|
||||||
-record(valid_frame, {command,
|
-record(valid_frame, {command, cl_trid, raw_frame}).
|
||||||
cl_trid,
|
|
||||||
raw_frame}).
|
-record(invalid_frame, {code, cl_trid, message}).
|
||||||
-record(invalid_frame, {code,
|
|
||||||
cl_trid,
|
-record(state, {socket, session_id, headers}).
|
||||||
message}).
|
|
||||||
-record(state, {socket,
|
|
||||||
session_id,
|
|
||||||
headers }).
|
|
||||||
|
|
||||||
-define(XMLErrorCode, <<"2001">>).
|
-define(XMLErrorCode, <<"2001">>).
|
||||||
|
|
||||||
-define(XMLErrorMessage, <<"Command syntax error.">>).
|
-define(XMLErrorMessage, <<"Command syntax error.">>).
|
||||||
|
|
||||||
%% Initialize process
|
%% Initialize process
|
||||||
|
@ -39,29 +39,25 @@ start_link(Socket) ->
|
||||||
handle_cast(serve, State = #state{socket = Socket}) ->
|
handle_cast(serve, State = #state{socket = Socket}) ->
|
||||||
NewState = state_from_socket(Socket, State),
|
NewState = state_from_socket(Socket, State),
|
||||||
{noreply, NewState};
|
{noreply, NewState};
|
||||||
|
|
||||||
%% Step two:
|
%% Step two:
|
||||||
%% Using the state of the connection, get the hello route from http server.
|
%% Using the state of the connection, get the hello route from http server.
|
||||||
%% Send the response from HTTP server back to EPP client.
|
%% Send the response from HTTP server back to EPP client.
|
||||||
%% When this succeeds, send "process_command" to self and await further
|
%% When this succeeds, send "process_command" to self and await further
|
||||||
%% commands.
|
%% commands.
|
||||||
handle_cast(greeting, State = #state{socket=Socket,
|
handle_cast(greeting,
|
||||||
session_id=SessionId,
|
State = #state{socket = Socket, session_id = SessionId,
|
||||||
headers = Headers}) ->
|
headers = Headers}) ->
|
||||||
|
Request = epp_http_client:request_builder(#{command =>
|
||||||
|
"hello",
|
||||||
Request = epp_http_client:request_builder(#{command => "hello",
|
|
||||||
session_id => SessionId,
|
session_id => SessionId,
|
||||||
raw_frame => "",
|
raw_frame => "",
|
||||||
headers => Headers,
|
headers => Headers,
|
||||||
cl_trid => nomatch}),
|
cl_trid => nomatch}),
|
||||||
|
|
||||||
{_Status, Body} = epp_http_client:request(Request),
|
{_Status, Body} = epp_http_client:request(Request),
|
||||||
|
|
||||||
frame_to_socket(Body, Socket),
|
frame_to_socket(Body, Socket),
|
||||||
gen_server:cast(self(), process_command),
|
gen_server:cast(self(), process_command),
|
||||||
{noreply, State#state{socket=Socket, session_id=SessionId}};
|
{noreply,
|
||||||
|
State#state{socket = Socket, session_id = SessionId}};
|
||||||
%% Step three to N:
|
%% Step three to N:
|
||||||
%% Await input from client. Parse it, and perform an appropriate http request.
|
%% Await input from client. Parse it, and perform an appropriate http request.
|
||||||
%% Commands go to commands, invalid XML goes to error.
|
%% Commands go to commands, invalid XML goes to error.
|
||||||
|
@ -75,19 +71,20 @@ handle_cast(process_command,
|
||||||
State = #state{socket = Socket, session_id = SessionId,
|
State = #state{socket = Socket, session_id = SessionId,
|
||||||
headers = Headers}) ->
|
headers = Headers}) ->
|
||||||
RawFrame = frame_from_socket(Socket, State),
|
RawFrame = frame_from_socket(Socket, State),
|
||||||
|
|
||||||
case parse_frame(RawFrame) of
|
case parse_frame(RawFrame) of
|
||||||
#valid_frame{command = Command, cl_trid = ClTRID} ->
|
#valid_frame{command = Command, cl_trid = ClTRID} ->
|
||||||
Request = epp_http_client:request_builder(#{command => Command,
|
Request = epp_http_client:request_builder(#{command =>
|
||||||
|
Command,
|
||||||
session_id => SessionId,
|
session_id => SessionId,
|
||||||
raw_frame => RawFrame,
|
raw_frame => RawFrame,
|
||||||
headers => Headers,
|
headers => Headers,
|
||||||
cl_trid => ClTRID}),
|
cl_trid => ClTRID}),
|
||||||
|
|
||||||
{_Status, Body} = epp_http_client:request(Request);
|
{_Status, Body} = epp_http_client:request(Request);
|
||||||
#invalid_frame{message=Message, code=Code, cl_trid=ClTRID} ->
|
#invalid_frame{message = Message, code = Code,
|
||||||
|
cl_trid = ClTRID} ->
|
||||||
Command = "error",
|
Command = "error",
|
||||||
Request = epp_http_client:request_builder(#{command => Command,
|
Request = epp_http_client:request_builder(#{command =>
|
||||||
|
Command,
|
||||||
session_id => SessionId,
|
session_id => SessionId,
|
||||||
headers => Headers,
|
headers => Headers,
|
||||||
code => Code,
|
code => Code,
|
||||||
|
@ -95,21 +92,20 @@ handle_cast(process_command,
|
||||||
cl_trid => ClTRID}),
|
cl_trid => ClTRID}),
|
||||||
{_Status, Body} = epp_http_client:error_request(Request)
|
{_Status, Body} = epp_http_client:error_request(Request)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
frame_to_socket(Body, Socket),
|
frame_to_socket(Body, Socket),
|
||||||
|
|
||||||
%% On logout, close the socket.
|
%% On logout, close the socket.
|
||||||
%% Else, go back to the beginning of the loop.
|
%% Else, go back to the beginning of the loop.
|
||||||
if
|
if Command =:= "logout" ->
|
||||||
Command =:= "logout" ->
|
|
||||||
ok = gen_tcp:shutdown(Socket, read_write),
|
ok = gen_tcp:shutdown(Socket, read_write),
|
||||||
{stop, normal, State};
|
{stop, normal, State};
|
||||||
true ->
|
true ->
|
||||||
gen_server:cast(self(), process_command),
|
gen_server:cast(self(), process_command),
|
||||||
{noreply, State#state{socket=Socket, session_id=SessionId}}
|
{noreply,
|
||||||
|
State#state{socket = Socket, session_id = SessionId}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
handle_call(_E, _From, State) -> {noreply, State}.
|
handle_call(_E, _From, State) -> {noreply, State}.
|
||||||
|
|
||||||
code_change(_OldVersion, State, _Extra) -> {ok, State}.
|
code_change(_OldVersion, State, _Extra) -> {ok, State}.
|
||||||
|
|
||||||
%% Private function
|
%% Private function
|
||||||
|
@ -120,20 +116,18 @@ read_length(Socket) ->
|
||||||
case gen_tcp:recv(Socket, 4) of
|
case gen_tcp:recv(Socket, 4) of
|
||||||
{ok, Data} ->
|
{ok, Data} ->
|
||||||
Length = binary:decode_unsigned(Data, big),
|
Length = binary:decode_unsigned(Data, big),
|
||||||
LengthToReceive = epp_util:frame_length_to_receive(Length),
|
LengthToReceive =
|
||||||
|
epp_util:frame_length_to_receive(Length),
|
||||||
{ok, LengthToReceive};
|
{ok, LengthToReceive};
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
io:format("Error: ~p~n", [Reason]),
|
io:format("Error: ~p~n", [Reason]), {error, Reason}
|
||||||
{error, Reason}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
read_frame(Socket, FrameLength) ->
|
read_frame(Socket, FrameLength) ->
|
||||||
case gen_tcp:recv(Socket, FrameLength) of
|
case gen_tcp:recv(Socket, FrameLength) of
|
||||||
{ok, Data} ->
|
{ok, Data} -> {ok, Data};
|
||||||
{ok, Data};
|
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
io:format("Error: ~p~n", [Reason]),
|
io:format("Error: ~p~n", [Reason]), {error, Reason}
|
||||||
{error, Reason}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% Wrap a message in EPP frame, and then send it to socket.
|
%% Wrap a message in EPP frame, and then send it to socket.
|
||||||
|
@ -148,25 +142,22 @@ state_from_socket(Socket, State) ->
|
||||||
{ok, {PeerIp, _PeerPort}} = inet:peername(Socket),
|
{ok, {PeerIp, _PeerPort}} = inet:peername(Socket),
|
||||||
Headers = [{"User-Agent", <<"EPP proxy">>},
|
Headers = [{"User-Agent", <<"EPP proxy">>},
|
||||||
{"X-Forwarded-for", epp_util:readable_ip(PeerIp)}],
|
{"X-Forwarded-for", epp_util:readable_ip(PeerIp)}],
|
||||||
NewState = State#state{socket=Socket, headers=Headers},
|
NewState = State#state{socket = Socket,
|
||||||
lager:info("Established connection with: [~p]~n", [NewState]),
|
headers = Headers},
|
||||||
|
lager:info("Established connection with: [~p]~n",
|
||||||
|
[NewState]),
|
||||||
NewState.
|
NewState.
|
||||||
|
|
||||||
%% First, listen for 4 bytes, then listen until the declared length.
|
%% First, listen for 4 bytes, then listen until the declared length.
|
||||||
%% Return the frame binary at the very end.
|
%% Return the frame binary at the very end.
|
||||||
frame_from_socket(Socket, State) ->
|
frame_from_socket(Socket, State) ->
|
||||||
Length = case read_length(Socket) of
|
Length = case read_length(Socket) of
|
||||||
{ok, Data} ->
|
{ok, Data} -> Data;
|
||||||
Data;
|
{error, _Details} -> {stop, normal, State}
|
||||||
{error, _Details} ->
|
|
||||||
{stop, normal, State}
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Frame = case read_frame(Socket, Length) of
|
Frame = case read_frame(Socket, Length) of
|
||||||
{ok, FrameData} ->
|
{ok, FrameData} -> FrameData;
|
||||||
FrameData;
|
{error, _FrameDetails} -> {stop, normal, State}
|
||||||
{error, _FrameDetails} ->
|
|
||||||
{stop, normal, State}
|
|
||||||
end,
|
end,
|
||||||
Frame.
|
Frame.
|
||||||
|
|
||||||
|
@ -177,11 +168,9 @@ parse_frame(Frame) ->
|
||||||
case epp_xml:parse(Frame) of
|
case epp_xml:parse(Frame) of
|
||||||
{ok, XMLRecord} ->
|
{ok, XMLRecord} ->
|
||||||
Command = epp_xml:get_command(XMLRecord),
|
Command = epp_xml:get_command(XMLRecord),
|
||||||
#valid_frame{command=Command,
|
#valid_frame{command = Command, cl_trid = ClTRID,
|
||||||
cl_trid=ClTRID,
|
|
||||||
raw_frame = Frame};
|
raw_frame = Frame};
|
||||||
{error, _} ->
|
{error, _} ->
|
||||||
#invalid_frame{code = ?XMLErrorCode,
|
#invalid_frame{code = ?XMLErrorCode,
|
||||||
message=?XMLErrorMessage,
|
message = ?XMLErrorMessage, cl_trid = ClTRID}
|
||||||
cl_trid=ClTRID}
|
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -3,39 +3,36 @@
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
-define(SERVER, ?MODULE).
|
-define(SERVER, ?MODULE).
|
||||||
|
|
||||||
-define(POOL_SUPERVISOR, epp_pool_supervisor).
|
-define(POOL_SUPERVISOR, epp_pool_supervisor).
|
||||||
|
|
||||||
-define(WORKER, epp_tls_worker).
|
-define(WORKER, epp_tls_worker).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
-export([init/1, handle_cast/2, handle_call/3, start_link/1]).
|
-export([handle_call/3, handle_cast/2, init/1,
|
||||||
|
start_link/1]).
|
||||||
|
|
||||||
-export([crl_file/0]).
|
-export([crl_file/0]).
|
||||||
|
|
||||||
-record(state, {socket, port, options}).
|
-record(state, {socket, port, options}).
|
||||||
|
|
||||||
start_link(Port) ->
|
start_link(Port) ->
|
||||||
gen_server:start_link({local, ?SERVER}, ?MODULE, Port, []).
|
gen_server:start_link({local, ?SERVER}, ?MODULE, Port,
|
||||||
|
[]).
|
||||||
|
|
||||||
init(Port) ->
|
init(Port) ->
|
||||||
Options = [binary,
|
Options = [binary, {packet, raw}, {active, false},
|
||||||
{packet, raw},
|
{reuseaddr, true}, {verify, verify_peer}, {depth, 1},
|
||||||
{active, false},
|
{cacertfile, ca_cert_file()}, {certfile, cert_file()},
|
||||||
{reuseaddr, true},
|
{keyfile, key_file()}, {crl_check, peer},
|
||||||
{verify, verify_peer},
|
{crl_cache,
|
||||||
{depth, 1},
|
{ssl_crl_cache, {internal, [{http, 5000}]}}}],
|
||||||
{cacertfile, ca_cert_file()},
|
|
||||||
{certfile, cert_file()},
|
|
||||||
{keyfile, key_file()},
|
|
||||||
{crl_check, peer},
|
|
||||||
{crl_cache, {ssl_crl_cache, {internal, [{http, 5000}]}}}],
|
|
||||||
|
|
||||||
ssl_crl_cache:insert({file, crl_file()}),
|
ssl_crl_cache:insert({file, crl_file()}),
|
||||||
|
|
||||||
{ok, ListenSocket} = ssl:listen(Port, Options),
|
{ok, ListenSocket} = ssl:listen(Port, Options),
|
||||||
gen_server:cast(self(), accept),
|
gen_server:cast(self(), accept),
|
||||||
|
{ok,
|
||||||
{ok, #state{socket=ListenSocket, port=Port, options=Options}}.
|
#state{socket = ListenSocket, port = Port,
|
||||||
|
options = Options}}.
|
||||||
|
|
||||||
%% Acceptor has only one state that goes in a loop:
|
%% Acceptor has only one state that goes in a loop:
|
||||||
%% 1. Listen for a connection from anyone.
|
%% 1. Listen for a connection from anyone.
|
||||||
|
@ -43,24 +40,26 @@ init(Port) ->
|
||||||
%% 3. Pass the connection to the worker and make it a controlling process for
|
%% 3. Pass the connection to the worker and make it a controlling process for
|
||||||
%% the socket.
|
%% the socket.
|
||||||
%% 4. Go back to listening.
|
%% 4. Go back to listening.
|
||||||
handle_cast(accept, State = #state{socket=ListenSocket, port=Port, options=Options}) ->
|
handle_cast(accept,
|
||||||
|
State = #state{socket = ListenSocket, port = Port,
|
||||||
|
options = Options}) ->
|
||||||
{ok, AcceptSocket} = ssl:transport_accept(ListenSocket),
|
{ok, AcceptSocket} = ssl:transport_accept(ListenSocket),
|
||||||
{ok, NewOwner} = create_worker(AcceptSocket),
|
{ok, NewOwner} = create_worker(AcceptSocket),
|
||||||
ok = ssl:controlling_process(AcceptSocket, NewOwner),
|
ok = ssl:controlling_process(AcceptSocket, NewOwner),
|
||||||
gen_server:cast(NewOwner, serve),
|
gen_server:cast(NewOwner, serve),
|
||||||
gen_server:cast(NewOwner, greeting),
|
gen_server:cast(NewOwner, greeting),
|
||||||
gen_server:cast(self(), accept),
|
gen_server:cast(self(), accept),
|
||||||
{noreply, State#state{socket=ListenSocket, port=Port, options=Options}}.
|
{noreply,
|
||||||
|
State#state{socket = ListenSocket, port = Port,
|
||||||
|
options = Options}}.
|
||||||
|
|
||||||
handle_call(_E, _From, State) -> {noreply, State}.
|
handle_call(_E, _From, State) -> {noreply, State}.
|
||||||
|
|
||||||
%% Create a worker process. These are short lived and should not be restarted,
|
%% Create a worker process. These are short lived and should not be restarted,
|
||||||
%% but for the purpose of order we should put them in a supervision tree.
|
%% but for the purpose of order we should put them in a supervision tree.
|
||||||
create_worker(Socket) ->
|
create_worker(Socket) ->
|
||||||
ChildSpec = #{id => rand:uniform(),
|
ChildSpec = #{id => rand:uniform(), type => worker,
|
||||||
type => worker,
|
modules => [?WORKER], restart => temporary,
|
||||||
modules => [?WORKER],
|
|
||||||
restart => temporary,
|
|
||||||
start => {?WORKER, start_link, [Socket]}},
|
start => {?WORKER, start_link, [Socket]}},
|
||||||
supervisor:start_child(?POOL_SUPERVISOR, ChildSpec).
|
supervisor:start_child(?POOL_SUPERVISOR, ChildSpec).
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
-module(epp_tls_worker).
|
-module(epp_tls_worker).
|
||||||
|
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
-define(SERVER, ?MODULE).
|
-define(SERVER, ?MODULE).
|
||||||
|
|
||||||
-include("epp_proxy.hrl").
|
-include("epp_proxy.hrl").
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
-export([init/1, handle_cast/2, handle_call/3, start_link/1]).
|
-export([handle_call/3, handle_cast/2, init/1,
|
||||||
|
start_link/1]).
|
||||||
|
|
||||||
-export([code_change/3]).
|
-export([code_change/3]).
|
||||||
|
|
||||||
-record(valid_frame, {command,
|
-record(valid_frame, {command, cl_trid, raw_frame}).
|
||||||
cl_trid,
|
|
||||||
raw_frame}).
|
-record(invalid_frame, {code, cl_trid, message}).
|
||||||
-record(invalid_frame, {code,
|
|
||||||
cl_trid,
|
-record(state, {socket, session_id, headers}).
|
||||||
message}).
|
|
||||||
-record(state, {socket,
|
|
||||||
session_id,
|
|
||||||
headers }).
|
|
||||||
|
|
||||||
-define(XMLErrorCode, <<"2001">>).
|
-define(XMLErrorCode, <<"2001">>).
|
||||||
|
|
||||||
-define(XMLErrorMessage, <<"Command syntax error.">>).
|
-define(XMLErrorMessage, <<"Command syntax error.">>).
|
||||||
|
|
||||||
%% Initialize process
|
%% Initialize process
|
||||||
|
@ -35,37 +35,33 @@ start_link(Socket) ->
|
||||||
%% First step after spinning off the process:
|
%% First step after spinning off the process:
|
||||||
%% Perform an TLS handshake and gather data that we use a headers for
|
%% Perform an TLS handshake and gather data that we use a headers for
|
||||||
%% http request:
|
%% http request:
|
||||||
%% Common name
|
%% Common name,
|
||||||
%% Client certificate
|
%% Client certificate,
|
||||||
%% Client IP address
|
%% Client IP address,
|
||||||
%% If certificate is revoked, this will fail right away here.
|
%% If certificate is revoked, this will fail right away here.
|
||||||
%% mod_epp does exactly the same thing.
|
%% mod_epp does exactly the same thing.
|
||||||
handle_cast(serve, State = #state{socket = Socket}) ->
|
handle_cast(serve, State = #state{socket = Socket}) ->
|
||||||
{ok, SecureSocket} = ssl:handshake(Socket),
|
{ok, SecureSocket} = ssl:handshake(Socket),
|
||||||
NewState = state_from_socket(SecureSocket, State),
|
NewState = state_from_socket(SecureSocket, State),
|
||||||
{noreply, NewState};
|
{noreply, NewState};
|
||||||
|
%% Step two: Using the state of the connection, get the hello route
|
||||||
%% Step two:
|
%% from http server. Send the response from HTTP server back to EPP
|
||||||
%% Using the state of the connection, get the hello route from http server.
|
%% client. When this succeeds, send "process_command" to self and
|
||||||
%% Send the response from HTTP server back to EPP client.
|
%% await further commands.
|
||||||
%% When this succeeds, send "process_command" to self and await further
|
handle_cast(greeting,
|
||||||
%% commands.
|
State = #state{socket = Socket, session_id = SessionId,
|
||||||
handle_cast(greeting, State = #state{socket=Socket,
|
|
||||||
session_id=SessionId,
|
|
||||||
headers = Headers}) ->
|
headers = Headers}) ->
|
||||||
|
Request = epp_http_client:request_builder(#{command =>
|
||||||
Request = epp_http_client:request_builder(#{command => "hello",
|
"hello",
|
||||||
session_id => SessionId,
|
session_id => SessionId,
|
||||||
raw_frame => "",
|
raw_frame => "",
|
||||||
headers => Headers,
|
headers => Headers,
|
||||||
cl_trid => nomatch}),
|
cl_trid => nomatch}),
|
||||||
|
|
||||||
{_Status, Body} = epp_http_client:request(Request),
|
{_Status, Body} = epp_http_client:request(Request),
|
||||||
|
|
||||||
frame_to_socket(Body, Socket),
|
frame_to_socket(Body, Socket),
|
||||||
gen_server:cast(self(), process_command),
|
gen_server:cast(self(), process_command),
|
||||||
{noreply, State#state{socket=Socket, session_id=SessionId}};
|
{noreply,
|
||||||
|
State#state{socket = Socket, session_id = SessionId}};
|
||||||
%% Step three to N:
|
%% Step three to N:
|
||||||
%% Await input from client. Parse it, and perform an appropriate http request.
|
%% Await input from client. Parse it, and perform an appropriate http request.
|
||||||
%% Commands go to commands, invalid XML goes to error.
|
%% Commands go to commands, invalid XML goes to error.
|
||||||
|
@ -79,19 +75,20 @@ handle_cast(process_command,
|
||||||
State = #state{socket = Socket, session_id = SessionId,
|
State = #state{socket = Socket, session_id = SessionId,
|
||||||
headers = Headers}) ->
|
headers = Headers}) ->
|
||||||
RawFrame = frame_from_socket(Socket, State),
|
RawFrame = frame_from_socket(Socket, State),
|
||||||
|
|
||||||
case parse_frame(RawFrame) of
|
case parse_frame(RawFrame) of
|
||||||
#valid_frame{command = Command, cl_trid = ClTRID} ->
|
#valid_frame{command = Command, cl_trid = ClTRID} ->
|
||||||
Request = epp_http_client:request_builder(#{command => Command,
|
Request = epp_http_client:request_builder(#{command =>
|
||||||
|
Command,
|
||||||
session_id => SessionId,
|
session_id => SessionId,
|
||||||
raw_frame => RawFrame,
|
raw_frame => RawFrame,
|
||||||
headers => Headers,
|
headers => Headers,
|
||||||
cl_trid => ClTRID}),
|
cl_trid => ClTRID}),
|
||||||
|
|
||||||
{_Status, Body} = epp_http_client:request(Request);
|
{_Status, Body} = epp_http_client:request(Request);
|
||||||
#invalid_frame{message=Message, code=Code, cl_trid=ClTRID} ->
|
#invalid_frame{message = Message, code = Code,
|
||||||
|
cl_trid = ClTRID} ->
|
||||||
Command = "error",
|
Command = "error",
|
||||||
Request = epp_http_client:request_builder(#{command => Command,
|
Request = epp_http_client:request_builder(#{command =>
|
||||||
|
Command,
|
||||||
session_id => SessionId,
|
session_id => SessionId,
|
||||||
headers => Headers,
|
headers => Headers,
|
||||||
code => Code,
|
code => Code,
|
||||||
|
@ -99,21 +96,20 @@ handle_cast(process_command,
|
||||||
cl_trid => ClTRID}),
|
cl_trid => ClTRID}),
|
||||||
{_Status, Body} = epp_http_client:error_request(Request)
|
{_Status, Body} = epp_http_client:error_request(Request)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
frame_to_socket(Body, Socket),
|
frame_to_socket(Body, Socket),
|
||||||
|
|
||||||
%% On logout, close the socket.
|
%% On logout, close the socket.
|
||||||
%% Else, go back to the beginning of the loop.
|
%% Else, go back to the beginning of the loop.
|
||||||
if
|
if Command =:= "logout" ->
|
||||||
Command =:= "logout" ->
|
|
||||||
ok = ssl:shutdown(Socket, read_write),
|
ok = ssl:shutdown(Socket, read_write),
|
||||||
{stop, normal, State};
|
{stop, normal, State};
|
||||||
true ->
|
true ->
|
||||||
gen_server:cast(self(), process_command),
|
gen_server:cast(self(), process_command),
|
||||||
{noreply, State#state{socket=Socket, session_id=SessionId}}
|
{noreply,
|
||||||
|
State#state{socket = Socket, session_id = SessionId}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
handle_call(_E, _From, State) -> {noreply, State}.
|
handle_call(_E, _From, State) -> {noreply, State}.
|
||||||
|
|
||||||
code_change(_OldVersion, State, _Extra) -> {ok, State}.
|
code_change(_OldVersion, State, _Extra) -> {ok, State}.
|
||||||
|
|
||||||
%% Private functions
|
%% Private functions
|
||||||
|
@ -121,18 +117,16 @@ read_length(Socket) ->
|
||||||
case ssl:recv(Socket, 4) of
|
case ssl:recv(Socket, 4) of
|
||||||
{ok, Data} ->
|
{ok, Data} ->
|
||||||
Length = binary:decode_unsigned(Data, big),
|
Length = binary:decode_unsigned(Data, big),
|
||||||
LengthToReceive = epp_util:frame_length_to_receive(Length),
|
LengthToReceive =
|
||||||
|
epp_util:frame_length_to_receive(Length),
|
||||||
{ok, LengthToReceive};
|
{ok, LengthToReceive};
|
||||||
{error, Reason} ->
|
{error, Reason} -> {error, Reason}
|
||||||
{error, Reason}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
read_frame(Socket, FrameLength) ->
|
read_frame(Socket, FrameLength) ->
|
||||||
case ssl:recv(Socket, FrameLength) of
|
case ssl:recv(Socket, FrameLength) of
|
||||||
{ok, Data} ->
|
{ok, Data} -> {ok, Data};
|
||||||
{ok, Data};
|
{error, Reason} -> {error, Reason}
|
||||||
{error, Reason} ->
|
|
||||||
{error, Reason}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% Wrap a message in EPP frame, and then send it to socket.
|
%% Wrap a message in EPP frame, and then send it to socket.
|
||||||
|
@ -142,25 +136,19 @@ frame_to_socket(Message, Socket) ->
|
||||||
write_line(Socket, ByteSize),
|
write_line(Socket, ByteSize),
|
||||||
write_line(Socket, Message).
|
write_line(Socket, Message).
|
||||||
|
|
||||||
write_line(Socket, Line) ->
|
write_line(Socket, Line) -> ok = ssl:send(Socket, Line).
|
||||||
ok = ssl:send(Socket, Line).
|
|
||||||
|
|
||||||
%% First, listen for 4 bytes, then listen until the declared length.
|
%% First, listen for 4 bytes, then listen until the declared length.
|
||||||
%% Return the frame binary at the very end.
|
%% Return the frame binary at the very end.
|
||||||
%% If the client closes connection abruptly, then kill the process
|
%% If the client closes connection abruptly, then kill the process
|
||||||
frame_from_socket(Socket, State) ->
|
frame_from_socket(Socket, State) ->
|
||||||
Length = case read_length(Socket) of
|
Length = case read_length(Socket) of
|
||||||
{ok, Data} ->
|
{ok, Data} -> Data;
|
||||||
Data;
|
{error, closed} -> log_and_exit(State)
|
||||||
{error, closed} ->
|
|
||||||
log_and_exit(State)
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Frame = case read_frame(Socket, Length) of
|
Frame = case read_frame(Socket, Length) of
|
||||||
{ok, FrameData} ->
|
{ok, FrameData} -> FrameData;
|
||||||
FrameData;
|
{error, closed} -> log_and_exit(State)
|
||||||
{error, closed} ->
|
|
||||||
log_and_exit(State)
|
|
||||||
end,
|
end,
|
||||||
Frame.
|
Frame.
|
||||||
|
|
||||||
|
@ -178,8 +166,10 @@ state_from_socket(Socket, State) ->
|
||||||
{"SSL-CLIENT-S-DN-CN", SSL_CLIENT_S_DN_CN},
|
{"SSL-CLIENT-S-DN-CN", SSL_CLIENT_S_DN_CN},
|
||||||
{"User-Agent", <<"EPP proxy">>},
|
{"User-Agent", <<"EPP proxy">>},
|
||||||
{"X-Forwarded-for", epp_util:readable_ip(PeerIp)}],
|
{"X-Forwarded-for", epp_util:readable_ip(PeerIp)}],
|
||||||
NewState = State#state{socket=Socket, headers=Headers},
|
NewState = State#state{socket = Socket,
|
||||||
lager:info("Established connection with: [~p]~n", [NewState]),
|
headers = Headers},
|
||||||
|
lager:info("Established connection with: [~p]~n",
|
||||||
|
[NewState]),
|
||||||
NewState.
|
NewState.
|
||||||
|
|
||||||
%% Get status, XML record, command and clTRID if defined.
|
%% Get status, XML record, command and clTRID if defined.
|
||||||
|
@ -189,11 +179,9 @@ parse_frame(Frame) ->
|
||||||
case epp_xml:parse(Frame) of
|
case epp_xml:parse(Frame) of
|
||||||
{ok, XMLRecord} ->
|
{ok, XMLRecord} ->
|
||||||
Command = epp_xml:get_command(XMLRecord),
|
Command = epp_xml:get_command(XMLRecord),
|
||||||
#valid_frame{command=Command,
|
#valid_frame{command = Command, cl_trid = ClTRID,
|
||||||
cl_trid=ClTRID,
|
|
||||||
raw_frame = Frame};
|
raw_frame = Frame};
|
||||||
{error, _} ->
|
{error, _} ->
|
||||||
#invalid_frame{code = ?XMLErrorCode,
|
#invalid_frame{code = ?XMLErrorCode,
|
||||||
message=?XMLErrorMessage,
|
message = ?XMLErrorMessage, cl_trid = ClTRID}
|
||||||
cl_trid=ClTRID}
|
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
-module(epp_util).
|
-module(epp_util).
|
||||||
|
|
||||||
-export([create_map/1, create_session_id/1, frame_length/1,
|
-export([create_map/1, create_session_id/1,
|
||||||
frame_length_to_receive/1, frame_length_to_send/1,
|
frame_length/1, frame_length_to_receive/1,
|
||||||
session_id/1, readable_ip/1]).
|
frame_length_to_send/1, readable_ip/1, session_id/1]).
|
||||||
|
|
||||||
-define(OFFSET, 4).
|
-define(OFFSET, 4).
|
||||||
|
|
||||||
%% Given a pid, return a sha512 hash of unique attributes.
|
%% Given a pid, return a sha512 hash of unique attributes.
|
||||||
-spec session_id(pid()) -> list(char()).
|
-spec session_id(pid()) -> [char()].
|
||||||
|
|
||||||
session_id(Pid) ->
|
session_id(Pid) ->
|
||||||
UniqueMap = create_map(Pid),
|
UniqueMap = create_map(Pid),
|
||||||
BinaryHash = create_session_id(UniqueMap),
|
BinaryHash = create_session_id(UniqueMap),
|
||||||
BinaryHash.
|
BinaryHash.
|
||||||
|
|
||||||
%% Give me a process id, I'll create a random map for you.
|
%% Give me a process id, I'll create a random map for you.
|
||||||
-spec create_map(pid()) -> #{string() => pid(), string() => float(),
|
-spec create_map(pid()) -> #{string() => pid(),
|
||||||
string() => string()}.
|
string() => float(), string() => string()}.
|
||||||
|
|
||||||
create_map(Pid) when is_pid(Pid) ->
|
create_map(Pid) when is_pid(Pid) ->
|
||||||
Now = erlang:system_time(second),
|
Now = erlang:system_time(second),
|
||||||
#{"pid" => Pid, "random" => rand:uniform(),
|
#{"pid" => Pid, "random" => rand:uniform(),
|
||||||
|
@ -23,25 +25,29 @@ create_map(Pid) when is_pid(Pid) ->
|
||||||
|
|
||||||
%% Given the special data structure, return back a binary hash to pass to the
|
%% Given the special data structure, return back a binary hash to pass to the
|
||||||
%% application server.
|
%% application server.
|
||||||
-spec create_session_id(#{string() => pid(), string() => float(),
|
-spec create_session_id(#{string() => pid(),
|
||||||
string() => string()}) -> list(char()).
|
string() => float(),
|
||||||
create_session_id(#{"pid" := Pid, "random" := Random, "timestamp" := Timestamp}) ->
|
string() => string()}) -> [char()].
|
||||||
Map = #{"pid" => pid_to_list(Pid), "random" => float_to_list(Random),
|
|
||||||
|
create_session_id(#{"pid" := Pid, "random" := Random,
|
||||||
|
"timestamp" := Timestamp}) ->
|
||||||
|
Map = #{"pid" => pid_to_list(Pid),
|
||||||
|
"random" => float_to_list(Random),
|
||||||
"timestamp" => Timestamp},
|
"timestamp" => Timestamp},
|
||||||
ListOfTuples = maps:to_list(Map),
|
ListOfTuples = maps:to_list(Map),
|
||||||
ListOfLists = [[X, ",", Y] || {X, Y} <- ListOfTuples],
|
ListOfLists = [[X, ",", Y] || {X, Y} <- ListOfTuples],
|
||||||
NestedList = lists:join(",", ListOfLists),
|
NestedList = lists:join(",", ListOfLists),
|
||||||
ListOfGlyphs = lists:flatten(NestedList),
|
ListOfGlyphs = lists:flatten(NestedList),
|
||||||
BinaryHash = crypto:hash(sha512, ListOfGlyphs),
|
BinaryHash = crypto:hash(sha512, ListOfGlyphs),
|
||||||
String = lists:flatten([integer_to_list(X,16) || <<X>> <= BinaryHash]),
|
String = lists:flatten([integer_to_list(X, 16)
|
||||||
|
|| <<X>> <= BinaryHash]),
|
||||||
String.
|
String.
|
||||||
|
|
||||||
frame_length_to_receive(Size) when Size >= 0 ->
|
frame_length_to_receive(Size) when Size >= 0 ->
|
||||||
Size - ?OFFSET.
|
Size - (?OFFSET).
|
||||||
|
|
||||||
frame_length_to_send(Frame) ->
|
frame_length_to_send(Frame) ->
|
||||||
Length = frame_length(Frame),
|
Length = frame_length(Frame), Length + (?OFFSET).
|
||||||
Length + ?OFFSET.
|
|
||||||
|
|
||||||
frame_length(Frame) when is_binary(Frame) ->
|
frame_length(Frame) when is_binary(Frame) ->
|
||||||
byte_size(Frame);
|
byte_size(Frame);
|
||||||
|
@ -50,8 +56,11 @@ frame_length(Frame) when is_list(Frame) ->
|
||||||
byte_size(Bin).
|
byte_size(Bin).
|
||||||
|
|
||||||
%% Pass a tuple of IP address, return a binary for sending over the wire.
|
%% Pass a tuple of IP address, return a binary for sending over the wire.
|
||||||
-spec readable_ip({integer(), integer(), integer(), integer()}) -> binary().
|
-spec readable_ip({integer(), integer(), integer(),
|
||||||
readable_ip({FirstOctet, SecondOctet, ThirdOctet, FourthOctet}) ->
|
integer()}) -> binary().
|
||||||
|
|
||||||
|
readable_ip({FirstOctet, SecondOctet, ThirdOctet,
|
||||||
|
FourthOctet}) ->
|
||||||
List = [integer_to_list(FirstOctet), ".",
|
List = [integer_to_list(FirstOctet), ".",
|
||||||
integer_to_list(SecondOctet), ".",
|
integer_to_list(SecondOctet), ".",
|
||||||
integer_to_list(ThirdOctet), ".",
|
integer_to_list(ThirdOctet), ".",
|
||||||
|
|
|
@ -5,8 +5,11 @@
|
||||||
-include_lib("xmerl/include/xmerl.hrl").
|
-include_lib("xmerl/include/xmerl.hrl").
|
||||||
|
|
||||||
%% Get command from an xmlElement. Otherwise return undefined.
|
%% Get command from an xmlElement. Otherwise return undefined.
|
||||||
get_command(Record) when is_record(Record, xmlElement) ->
|
get_command(Record)
|
||||||
case xmerl_xpath:string("name(/epp/command/*[1])", Record) of
|
when is_record(Record, xmlElement) ->
|
||||||
|
case xmerl_xpath:string("name(/epp/command/*[1])",
|
||||||
|
Record)
|
||||||
|
of
|
||||||
{xmlObj, string, []} -> undefined;
|
{xmlObj, string, []} -> undefined;
|
||||||
{xmlObj, string, Command} -> Command
|
{xmlObj, string, Command} -> Command
|
||||||
end;
|
end;
|
||||||
|
@ -16,14 +19,14 @@ get_command(_) -> undefined.
|
||||||
%% Otherwise return an error tuple.
|
%% Otherwise return an error tuple.
|
||||||
parse(Text) when is_list(Text) -> parse_list(Text);
|
parse(Text) when is_list(Text) -> parse_list(Text);
|
||||||
parse(Text) when is_binary(Text) ->
|
parse(Text) when is_binary(Text) ->
|
||||||
List = binary_to_list(Text),
|
List = binary_to_list(Text), parse_list(List);
|
||||||
parse_list(List);
|
|
||||||
parse(_) -> {error, {fatal, {expected_binary_or_list}}}.
|
parse(_) -> {error, {fatal, {expected_binary_or_list}}}.
|
||||||
|
|
||||||
%% Parse a record that came from the wire and return a xmlElement record.
|
%% Parse a record that came from the wire and return a xmlElement record.
|
||||||
parse_list(List) when is_list(List) ->
|
parse_list(List) when is_list(List) ->
|
||||||
try xmerl_scan:string(List, [{quiet, 'true'}]) of
|
try xmerl_scan:string(List, [{quiet, true}]) of
|
||||||
{Record, []} when is_record(Record, xmlElement) -> {ok, Record}
|
{Record, []} when is_record(Record, xmlElement) ->
|
||||||
|
{ok, Record}
|
||||||
catch
|
catch
|
||||||
exit:X -> {error, X}
|
exit:X -> {error, X}
|
||||||
end.
|
end.
|
||||||
|
@ -31,13 +34,16 @@ parse_list(List) when is_list(List) ->
|
||||||
%% The idea is that even when XML command is invalid,
|
%% The idea is that even when XML command is invalid,
|
||||||
%% we should recover cltrid from it, hence the regex use.
|
%% we should recover cltrid from it, hence the regex use.
|
||||||
-spec find_cltrid(any()) -> binary() | nomatch.
|
-spec find_cltrid(any()) -> binary() | nomatch.
|
||||||
|
|
||||||
find_cltrid(Text) when is_list(Text) -> run_regex(Text);
|
find_cltrid(Text) when is_list(Text) -> run_regex(Text);
|
||||||
find_cltrid(Text) when is_binary(Text) -> run_regex(Text);
|
find_cltrid(Text) when is_binary(Text) ->
|
||||||
|
run_regex(Text);
|
||||||
find_cltrid(_) -> nomatch.
|
find_cltrid(_) -> nomatch.
|
||||||
|
|
||||||
run_regex(Text) ->
|
run_regex(Text) ->
|
||||||
{ok, MP} = re:compile("<clTRID>(?<cltrid>.+)<\/clTRID>"),
|
{ok, MP} = re:compile("<clTRID>(?<cltrid>.+)</clTRID>"),
|
||||||
case re:run(Text, MP, [{capture, ["cltrid"], binary}]) of
|
case re:run(Text, MP, [{capture, ["cltrid"], binary}])
|
||||||
|
of
|
||||||
{match, [Binary]} -> Binary;
|
{match, [Binary]} -> Binary;
|
||||||
nomatch -> nomatch
|
nomatch -> nomatch
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -47,3 +47,5 @@
|
||||||
},
|
},
|
||||||
{test, [{deps, [proper]}]}]
|
{test, [{deps, [proper]}]}]
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
{plugins, [rebar3_fmt]}.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue