From 1b85a239457e0944a75a4423105a21ed151edb04 Mon Sep 17 00:00:00 2001 From: Ilya Khaprov Date: Fri, 9 Feb 2018 16:01:04 +0300 Subject: [PATCH] Parse soap 1.1 faults see: https://github.com/bet365/soap/issues/24 https://github.com/bet365/soap/issues/29 --- .gitignore | 1 + src/soap_client_util.erl | 5 +- src/soap_fault.erl | 196 +++++++++++++++++++++------------------ 3 files changed, 108 insertions(+), 94 deletions(-) diff --git a/.gitignore b/.gitignore index fd79704..11c833a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ rel/example_project *~ logs rebar.lock +.rebar3 \ No newline at end of file diff --git a/src/soap_client_util.erl b/src/soap_client_util.erl index a85840b..60f25b9 100644 --- a/src/soap_client_util.erl +++ b/src/soap_client_util.erl @@ -256,7 +256,7 @@ parse_mime(Message, Model, Http_status, Http_headers, parse_xml(Message, Model, Http_status, Http_headers, Version, Ns, Handler, Attachments, HTTP_body) -> - try erlsom:parse_sax(Message, + try erlsom:parse_sax(Message, #p_state{model = Model, version = Version, soap_ns = Ns, state = start, handler = Handler}, @@ -356,8 +356,9 @@ xml_parser_cb({startElement, Ns, "Fault", _Prfx, _Attrs} = Event, #p_state{state = body, version = Version, namespaces = Namespaces, + model = Model, soap_ns = Ns} = S) -> - Start_state = soap_fault:parse_fault_start(Version), + Start_state = soap_fault:parse_fault_start(Version, Model), Fault_parser = fun soap_fault:parse_fault/3, S1 = parse_event(Fault_parser, startDocument, Namespaces, Start_state), %% the event that we just received from the sax parser is recycled diff --git a/src/soap_fault.erl b/src/soap_fault.erl index 8c1c37e..8608b5d 100644 --- a/src/soap_fault.erl +++ b/src/soap_fault.erl @@ -18,7 +18,7 @@ %% %CopyrightEnd% %% -%%% +%%% %%% functions to create soap faults %%% -module(soap_fault). @@ -39,7 +39,7 @@ %% used by other modules of the framework -export([parse_fault/3]). --export([parse_fault_start/1]). +-export([parse_fault_start/2]). %% to test: -export([fault_1_1/4, fault_1_2/4]). @@ -74,7 +74,10 @@ -record(pf_state, { version :: atom(), + model :: atom(), state :: atom(), + parser :: any(), + parser_state :: any(), characters = "" :: string(), code :: fault_code_object(), fault_string :: fault_string(), @@ -86,9 +89,9 @@ }). -record(attribute, { - localName, - prefix, - uri, + localName, + prefix, + uri, value}). %%% ============================================================================ @@ -100,12 +103,12 @@ fault(Fault_code, Fault_string, Soap_req) -> fault(Fault_code, Fault_string, [], undefined, Soap_req). --spec fault(fault_code(), fault_string(), [fault_detail()], +-spec fault(fault_code(), fault_string(), [fault_detail()], soap_req()) -> iolist(). fault(Fault_code, Fault_string, Details, Soap_req) -> fault(Fault_code, Fault_string, Details, undefined, Soap_req). --spec fault(fault_code(), fault_string(), [fault_detail()], +-spec fault(fault_code(), fault_string(), [fault_detail()], fault_actor(), soap_req()) -> iolist(). fault(F_code, F_string, F_details, F_actor, Soap_req) -> case soap_req:soap_version(Soap_req) of @@ -119,26 +122,26 @@ fault(F_code, F_string, F_details, F_actor, Soap_req) -> code(Code) when is_atom(Code) -> #faultcode{code = Code}. --spec code(Uri::string(), Code::string()) -> +-spec code(Uri::string(), Code::string()) -> fault_code_object(). code(Namespace, Code) -> #faultcode{uri = xml_string(Namespace), code = xml_string(Code)}. --spec code_with_subcode(Code::fault_code_atom(), Subcode::fault_code()) -> +-spec code_with_subcode(Code::fault_code_atom(), Subcode::fault_code()) -> fault_code_object(). -code_with_subcode(Code, Subcode) +code_with_subcode(Code, Subcode) when is_atom(Code), is_record(Subcode, faultcode) -> #faultcode{code = Code, subcode = Subcode}. --spec code_with_subcode(Uri::string(), Code::string(), +-spec code_with_subcode(Uri::string(), Code::string(), Subcode::fault_code()) -> fault_code(). code_with_subcode(Uri, Code, Subcode) -> - #faultcode{uri = xml_string(Uri), - code = xml_string(Code), + #faultcode{uri = xml_string(Uri), + code = xml_string(Code), subcode = Subcode}. --spec reason(Reason::string(), Language::string()) -> +-spec reason(Reason::string(), Language::string()) -> fault_reason(). reason(Reason, Language) -> #fault_reason{text = xml_string(Reason), lang = xml_string(Language)}. @@ -146,18 +149,19 @@ reason(Reason, Language) -> -spec detail(Namespace::string(), Prefix::string(), Tag::string(), Text::string()) -> fault_detail(). detail(Namespace, Prefix, Tag, Text) -> - [<<"<">>, Prefix, <<":">>, Tag, <<" xmlns:">>, Prefix, <<"=\"">>, + [<<"<">>, Prefix, <<":">>, Tag, <<" xmlns:">>, Prefix, <<"=\"">>, Namespace, <<"\">">>, xml_string(Text), <<">, Prefix, <<":">>, Tag, <<">">>]. --spec parse_fault_start(Version:: '1.1' | '1.2') -> any(). -parse_fault_start(Version) -> - #pf_state{version = Version, +-spec parse_fault_start(Version:: '1.1' | '1.2', any()) -> any(). +parse_fault_start(Version, Model) -> + #pf_state{version = Version, + model = Model, state = start}. -spec parse_fault(Event::erlsom:sax_event(), any(), State::any()) -> any(). parse_fault(Event, Namespaces, #pf_state{version = '1.1'} = State) -> parse_fault_1_1(Event, Namespaces, State); - + parse_fault(Event, Namespaces, #pf_state{version = '1.2'} = State) -> parse_fault_1_2(Event, Namespaces, State). @@ -169,11 +173,11 @@ parse_fault(Event, Namespaces, #pf_state{version = '1.2'} = State) -> %%% Creating faults %%% ---------------------------------------------------------------------------- -fault_1_1(Fault_code, Fault_string, Details, Fault_actor) +fault_1_1(Fault_code, Fault_string, Details, Fault_actor) when is_binary(Fault_string) -> fault_1_1(Fault_code, [Fault_string], Details, Fault_actor); fault_1_1(Fault_code, Fault_string, Details, Fault_actor) - when is_list(Fault_string) andalso + when is_list(Fault_string) andalso ((length(Fault_string) == 0) orelse not is_tuple(hd(Fault_string))) -> Code = fault_code(Fault_code, '1.1'), Actor = fault_actor(Fault_actor, '1.1'), @@ -190,7 +194,7 @@ fault_1_1(Fault_code, Fault_string, Details, Fault_actor) fault_1_2(Fault_code, Fault_strings, Details, Fault_actor) -> Code = fault_code(Fault_code, '1.2'), Role = fault_actor(Fault_actor, '1.2'), - Texts = make_reasons(Fault_strings), + Texts = make_reasons(Fault_strings), [<<"">>, Code, <<"">>, @@ -200,44 +204,44 @@ fault_1_2(Fault_code, Fault_strings, Details, Fault_actor) -> fault_detail(Details, '1.2'), <<"">>]. -%% somewhat confusingly, Fault_strings can be +%% somewhat confusingly, Fault_strings can be %% - a single #fault_reason{} record -%% - a list of #fault_reason{} records, +%% - a list of #fault_reason{} records, %% - a string (i.e. a list of characters). -make_reasons(Fault_string) +make_reasons(Fault_string) when is_tuple(Fault_string) -> - make_reason(Fault_string); -make_reasons(Fault_strings) + make_reason(Fault_string); +make_reasons(Fault_strings) when is_list(Fault_strings) andalso ((length(Fault_strings) == 0) orelse is_tuple(hd(Fault_strings))) -> - [make_reason(Text) || Text <- Fault_strings]; + [make_reason(Text) || Text <- Fault_strings]; make_reasons(Fault_string) -> make_reason(#fault_reason{text = Fault_string}). make_reason(#fault_reason{text = Text, lang = Lang}) -> - [<<">, xml_string(Lang), + [<<">, xml_string(Lang), <<"\">">>, xml_string(Text), <<"">>]. fault_code(Code, Version) when is_atom(Code) -> Code_string = fault_code_text(Code, Version), - case Version of + case Version of '1.1' -> - [<<"">>, Code_string, <<"">>]; + [<<"">>, Code_string, <<"">>]; '1.2' -> - [<<"">>, Code_string, + [<<"">>, Code_string, <<"">>] end; -fault_code(#faultcode{code = Code}, Version) - when Version == '1.1', +fault_code(#faultcode{code = Code}, Version) + when Version == '1.1', is_atom(Code) -> - [<<"">>, fault_code_text(Code, Version), <<"">>]; -fault_code(#faultcode{uri = Namespace, code = Code}, Version) + [<<"">>, fault_code_text(Code, Version), <<"">>]; +fault_code(#faultcode{uri = Namespace, code = Code}, Version) when Version == '1.1' -> [<<">, xml_string(Namespace), <<"\">F_CODE:">>, - xml_string(Code), <<"">>]; + xml_string(Code), <<"">>]; fault_code(#faultcode{code = Code, subcode = Subcode}, Version) when Version == '1.2' -> - [<<"">>, fault_code_text(Code, Version), + [<<"">>, fault_code_text(Code, Version), <<"">>, subcode_text(Subcode), <<"">>]. @@ -247,38 +251,38 @@ fault_code_text(Code, Version) when is_atom(Code) -> fault_code_text(Code, Version) when not is_atom(Code) -> fault_code_text_2(unicode:characters_to_list(Code), Version). -fault_code_text_2(Code, '1.1') +fault_code_text_2(Code, '1.1') when Code == server; Code == "Server" -> - <<"SOAP-ENV:Server">>; + <<"SOAP-ENV:Server">>; fault_code_text_2(Code, '1.2') when Code == server; Code == "Receiver" -> - <<"SOAP-ENV:Receiver">>; + <<"SOAP-ENV:Receiver">>; fault_code_text_2(Code, '1.1') when Code == client; Code == "Client" -> - <<"SOAP-ENV:Client">>; + <<"SOAP-ENV:Client">>; fault_code_text_2(Code, '1.2') when Code == client; Code == "Sender" -> - <<"SOAP-ENV:Sender">>; + <<"SOAP-ENV:Sender">>; fault_code_text_2(Code, _) %% version 1.2 when Code == version_mismatch; Code == "VersionMismatch" -> - <<"SOAP-ENV:VersionMismatch">>; + <<"SOAP-ENV:VersionMismatch">>; fault_code_text_2(Code, _) %% version 1.2 when Code == must_understand; Code == "MustUnderstand" -> - <<"SOAP-ENV:MustUnderstand">>; + <<"SOAP-ENV:MustUnderstand">>; fault_code_text_2(Code, _) %% version 1.2 when Code == data_encoding_unknown; Code == "DataEncodingUnknown" -> <<"SOAP-ENV:DataEncodingUnknown">>. subcode_text(#faultcode{uri = Namespace, code = Text}) -> - [<<">, Namespace, - <<"\">SUB:">>, xml_string(Text), + [<<">, Namespace, + <<"\">SUB:">>, xml_string(Text), <<"">>]. fault_actor(undefined, _) -> @@ -305,38 +309,39 @@ make_code(String, N_spaces) -> [Prefix, Local] -> case lists:keyfind(Prefix, 1, N_spaces) of false -> - #faultcode{uri = "", + #faultcode{uri = "", code = String}; {_, Uri} -> - #faultcode{uri = Uri, + #faultcode{uri = Uri, code = Local} end; _ -> - #faultcode{uri = "", + #faultcode{uri = "", code = String} end. %%% ---------------------------------------------------------------------------- %%% Parsing faults %%% -%%% These are erlsom:sax callback functions. The reason to use custom parsers +%%% These are erlsom:sax callback functions. The reason to use custom parsers %%% is that none of the existing erlsom sax parsers make it possible to deal %%% with qnames ("SUB:code"). %%% ---------------------------------------------------------------------------- parse_fault_1_1(startDocument, _Namespaces, #pf_state{state = start} = S) -> S#pf_state{state = start}; -parse_fault_1_1({startElement, ?SOAP_NS, "Fault", _, _}, +parse_fault_1_1({startElement, ?SOAP_NS, "Fault", _, _}, _Namespaces, #pf_state{state = start} = S) -> S#pf_state{state = start}; -parse_fault_1_1({startElement, _, "faultcode", _, _}, +parse_fault_1_1({startElement, _, "faultcode", _, _}, _Namespaces, #pf_state{state = start} = S) -> S#pf_state{state = code}; -parse_fault_1_1({characters, Characters}, +parse_fault_1_1({characters, Characters}, _Namespaces, - #pf_state{characters = String} = S) -> + #pf_state{characters = String, + state = State} = S) when State =/= parsing_details -> S#pf_state{characters = String ++ Characters}; parse_fault_1_1({endElement, _, "faultcode", _}, Namespaces, @@ -345,7 +350,7 @@ parse_fault_1_1({endElement, _, "faultcode", _}, S#pf_state{code = make_code(String, Namespaces), characters = "", state = code_done}; -parse_fault_1_1({startElement, _, "faultstring", _, _}, +parse_fault_1_1({startElement, _, "faultstring", _, _}, _Namespaces, #pf_state{state = code_done} = S) -> S#pf_state{state = faultstring}; @@ -356,7 +361,7 @@ parse_fault_1_1({endElement, _, "faultstring", _}, S#pf_state{fault_string = String, characters = "", state = faultstring_done}; -parse_fault_1_1({startElement, _, "faultactor", _, _}, +parse_fault_1_1({startElement, _, "faultactor", _, _}, _Namespaces, #pf_state{state = State} = S) when State == faultstring_done; @@ -369,35 +374,42 @@ parse_fault_1_1({endElement, _, "faultactor", _}, S#pf_state{actor = String, characters = "", state = actor_done}; -parse_fault_1_1({startElement, _, "detail", _, _}, +parse_fault_1_1({startElement, _, "detail", _, _}, _Namespaces, #pf_state{state = State} = S) when State == faultstring_done; State == code_done; State == actor_done -> S#pf_state{state = details}; -parse_fault_1_1({startElement, Namespace, Tag, _, _}, - _Namespaces, - #pf_state{state = details} = S) -> - S#pf_state{state = detail, +%% detail child start +parse_fault_1_1({startElement, Namespace, Tag, _, _} = Event, + Namespaces, + #pf_state{state = details, + model = Model} = S) -> + + Callback_state = erlsom_parse:new_state(Model, Namespaces), + %% a new "startDocument" event is injected to get the body parser going. + S1 = erlsom_parse:xml2StructCallback(startDocument, Callback_state), + S2 = erlsom_parse:xml2StructCallback(Event, S1), + S#pf_state{state = parsing_details, parser_state = S2, + parser = fun erlsom_parse:xml2StructCallback/2, detail_tag = {Tag, Namespace}}; -parse_fault_1_1({endElement, Namespace, Tag, _}, - _Namespaces, - #pf_state{state = detail, - details = Details, - detail_tag = {Tag, Namespace}, - characters = String} = S) -> - S#pf_state{details = [#faultdetail{tag = Tag, - uri = Namespace, - text = String} | Details], - characters = "", - state = details}; parse_fault_1_1({endElement, _, "detail", _}, _Namespaces, - #pf_state{state = details, - details = Details} = S) -> - S#pf_state{details = lists:reverse(Details), + #pf_state{state = parsing_details, + parser = Parser, + parser_state = P_state, + details = _Details} = S) -> + Parsed_fault = Parser(endDocument, P_state), + S#pf_state{details = Parsed_fault, state = details_done}; +parse_fault_1_1(Event, + _Namespaces, + #pf_state{state = parsing_details, + parser = Parser, + parser_state = P_state} = S) -> + S#pf_state{parser_state = Parser(Event, P_state)}; +%% detail child end parse_fault_1_1({endElement, ?SOAP_NS, "Fault", _}, _Namespaces, #pf_state{state = State} = S) @@ -412,23 +424,23 @@ parse_fault_1_1(endDocument, _Namespaces, parse_fault_1_2(startDocument, _Namespaces, #pf_state{state = start} = S) -> S#pf_state{state = start}; -parse_fault_1_2({startElement, ?SOAP12_NS, "Fault", _, _}, +parse_fault_1_2({startElement, ?SOAP12_NS, "Fault", _, _}, _Namespaces, #pf_state{state = start} = S) -> S#pf_state{state = start}; -parse_fault_1_2({startElement, ?SOAP12_NS, "Code", _, _}, +parse_fault_1_2({startElement, ?SOAP12_NS, "Code", _, _}, _Namespaces, #pf_state{state = start} = S) -> S#pf_state{state = code}; -parse_fault_1_2({startElement, ?SOAP12_NS, "Value", _, _}, +parse_fault_1_2({startElement, ?SOAP12_NS, "Value", _, _}, _Namespaces, #pf_state{state = code} = S) -> S#pf_state{state = code_value}; -parse_fault_1_2({startElement, ?SOAP12_NS, "Value", _, _}, +parse_fault_1_2({startElement, ?SOAP12_NS, "Value", _, _}, _Namespaces, #pf_state{state = subcode} = S) -> S#pf_state{state = subcode_value}; -parse_fault_1_2({characters, Characters}, +parse_fault_1_2({characters, Characters}, _Namespaces, #pf_state{characters = String} = S) -> S#pf_state{characters = String ++ Characters}; @@ -439,7 +451,7 @@ parse_fault_1_2({endElement, ?SOAP12_NS, "Value", _}, S#pf_state{code = make_code(String, Namespaces), characters = "", state = value_done}; -parse_fault_1_2({startElement, ?SOAP12_NS, "Subcode", _, _}, +parse_fault_1_2({startElement, ?SOAP12_NS, "Subcode", _, _}, _Namespaces, #pf_state{state = value_done} = S) -> S#pf_state{state = subcode}; @@ -457,11 +469,11 @@ parse_fault_1_2({endElement, _, "Subcode", _}, S#pf_state{state = subcode_done}; parse_fault_1_2({endElement, ?SOAP12_NS, "Code", _}, _Namespaces, - #pf_state{state = State} = S) + #pf_state{state = State} = S) when State == subcode_done; State == value_done -> S#pf_state{state = code_done}; -parse_fault_1_2({startElement, ?SOAP12_NS, "Reason", _, _}, +parse_fault_1_2({startElement, ?SOAP12_NS, "Reason", _, _}, _Namespaces, #pf_state{state = code_done} = S) -> S#pf_state{state = reasons}; @@ -471,16 +483,16 @@ parse_fault_1_2({endElement, ?SOAP12_NS, "Reason", _}, reasons = Reasons} = S) -> S#pf_state{reasons = lists:reverse(Reasons), state = reasons_done}; -parse_fault_1_2({startElement, ?SOAP12_NS, "Text", _, Attributes}, +parse_fault_1_2({startElement, ?SOAP12_NS, "Text", _, Attributes}, _Namespaces, #pf_state{state = reasons} = S) -> - Language = + Language = case lists:keyfind("lang", #attribute.localName, Attributes) of #attribute{prefix = "xml", value = L} -> L; _Other -> undefined - end, + end, S#pf_state{language = Language, state = text}; parse_fault_1_2({endElement, ?SOAP12_NS, "Text", _}, _Namespaces, @@ -492,7 +504,7 @@ parse_fault_1_2({endElement, ?SOAP12_NS, "Text", _}, text = String} | Reasons], characters = "", state = reasons}; -parse_fault_1_2({startElement, ?SOAP12_NS, "Role", _, _}, +parse_fault_1_2({startElement, ?SOAP12_NS, "Role", _, _}, _Namespaces, #pf_state{state = State} = S) when State == reasons_done; @@ -505,14 +517,14 @@ parse_fault_1_2({endElement, ?SOAP12_NS, "Role", _}, S#pf_state{actor = String, characters = "", state = role_done}; -parse_fault_1_2({startElement, ?SOAP12_NS, "Detail", _, _}, +parse_fault_1_2({startElement, ?SOAP12_NS, "Detail", _, _}, _Namespaces, #pf_state{state = State} = S) when State == reasons_done; State == code_done; State == role_done -> S#pf_state{state = details}; -parse_fault_1_2({startElement, Namespace, Tag, _, _}, +parse_fault_1_2({startElement, Namespace, Tag, _, _}, _Namespaces, #pf_state{state = details} = S) -> S#pf_state{state = detail, @@ -523,8 +535,8 @@ parse_fault_1_2({endElement, Namespace, Tag, _}, details = Details, detail_tag = {Tag, Namespace}, characters = String} = S) -> - S#pf_state{details = [#faultdetail{tag = Tag, - uri = Namespace, + S#pf_state{details = [#faultdetail{tag = Tag, + uri = Namespace, text = String} | Details], characters = "", state = details};