123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473(*
* Copyright (c) 2006-2009 Citrix Systems Inc.
* Copyright (c) 2006-2014 Thomas Gazagnaire <thomas@gazagnaire.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)openPrintfopenRpc(* marshalling/unmarshalling code *)(* The XML-RPC is not very clear about what characters can be in a string value ... *)letencode=lettranslate=function|'>'->Some">"|'<'->Some"<"|'&'->Some"&"|'"'->Some"""|cwhen(c>='\x20'&&c<='\xff')||c='\x09'||c='\x0a'||c='\x0d'->None|_->Some""inInternals.encodetranslateletrecadd_value?(strict=false)f=function|Null->f"<value><nil/></value>"|Inti->f"<value>";ifstrictthenf"<i8>";f(Int64.to_stringi);ifstrictthenf"</i8>";f"</value>"|Int32i->f"<value><i4>";f(Int32.to_stringi);f"</i4></value>"|Boolb->f"<value><boolean>";f(ifbthen"1"else"0");f"</boolean></value>"|Floatd->f"<value><double>";(* NB: "%g" loses a lot of precision (e.g. resulting in "1.32621e+09") *)f(Printf.sprintf"%.16g"d);f"</double></value>"|Strings->f"<value>";f(encodes);f"</value>"|DateTimes->f"<value><dateTime.iso8601>";fs;f"</dateTime.iso8601></value>"|Base64s->f"<value><base64>";f(Base64.encode_exns);f"</base64></value>"|Enuml->f"<value><array><data>";List.iter(add_value~strictf)l;f"</data></array></value>"|Dictd->letadd_member(name,value)=f"<member><name>";fname;f"</name>";add_value~strictfvalue;f"</member>"inf"<value><struct>";List.iteradd_memberd;f"</struct></value>"letto_string?(strict=false)x=letbuf=Buffer.create128inadd_value~strict(Buffer.add_stringbuf)x;Buffer.contentsbufletto_a?(strict=false)~empty~appendx=letbuf=empty()inadd_value~strict(funs->appendbufs)x;bufletstring_of_call?(strict=false)call=letmoduleB=Bufferinletbuf=B.create1024inletadd=B.add_stringbufinadd"<?xml version=\"1.0\"?>";add"<methodCall><methodName>";add(encodecall.name);add"</methodName><params>";List.iter(funp->add"<param>";add(to_string~strictp);add"</param>")call.params;add"</params></methodCall>";B.contentsbufletadd_response?(strict=false)addresponse=letv=ifresponse.successthenDict["Status",String"Success";"Value",response.contents]elseDict["Status",String"Failure";"ErrorDescription",response.contents]inadd"<?xml version=\"1.0\"?><methodResponse><params><param>";to_a~strict~empty:(fun()->())~append:(fun_s->adds)v;add"</param></params></methodResponse>"letstring_of_response?(strict=false)response=letmoduleB=Bufferinletbuf=B.create256inletadd=B.add_stringbufinadd_response~strictaddresponse;B.contentsbufleta_of_response?(strict=false)~empty~appendresponse=letbuf=empty()inletadds=appendbufsinadd_response~strictaddresponse;bufexceptionParse_errorofstring*string*Xmlm.inputletdebug_inputinput=letbuf=Buffer.create1024inletrecauxtags=ifnot(Xmlm.eoiinput)then(matchXmlm.inputinputwith|`El_start((_,tag),_)->Buffer.add_stringbuf"<";Buffer.add_stringbuftag;Buffer.add_stringbuf">";aux(tag::tags)|`El_end->(matchtagswith|[]->Buffer.add_stringbuf"<?/>";auxtags|h::t->Buffer.add_stringbuf"</";Buffer.add_stringbufh;Buffer.add_stringbuf">";auxt)|`Datad->Buffer.add_stringbufd;auxtags|`Dtd_->auxtags)inaux[];Buffer.contentsbufletpretty_string_of_errorgotexpectedinput=sprintf"Error: got '%s' while '%s' was expected when processing '%s'\n"gotexpected(debug_inputinput)letparse_errorgotexpectedinput=raise(Parse_error(got,expected,input))moduleParser=structletis_emptys=letis_empty=reftrueinfori=0toString.lengths-1doifs.[i]<>'\n'&&s.[i]<>' '&&s.[i]<>'\t'thenis_empty:=falsedone;!is_emptyletrecskip_emptyinput=matchXmlm.peekinputwith|`Datadwhenis_emptyd->let_=Xmlm.inputinputinskip_emptyinput|_->()(* Helpers *)letget_datainput=matchXmlm.inputinputwith|`Dtd_->parse_error"dtd""data"input|`Datad->d|`El_start((_,tag),_)->parse_error(sprintf"open_tag(%s)"tag)"data"input|`El_end->""letrecopen_taginput=matchXmlm.inputinputwith|`Dtd_->open_taginput|`El_start((_,tag),_)->tag|`Datadwhenis_emptyd->open_taginput|`Datad->parse_error(sprintf"data(%s)"(String.escapedd))"open_tag"input|`El_end->parse_error"close_tag""open_tag"inputletrecclose_tagtaginput=matchXmlm.inputinputwith|`Dtd_->parse_error"dtd"(sprintf"close_tag(%s)"tag)input|`El_end->()|`El_start((_,t),_)->parse_error(sprintf"open_tag(%s)"t)(sprintf"close_tag(%s)"tag)input|`Datadwhenis_emptyd->close_tagtaginput|`Datad->parse_error(sprintf"data(%s)"(String.escapedd))(sprintf"close_tag(%s)"tag)inputletempty_taginput=function|"string"->String""|"array"->Enum[]|"struct"->Dict[]|"nil"->Null|"value"->String""|tag->parse_error(sprintf"empty_%s"tag)taginputletmap_tagsfinput=lettag=open_taginputinletr=ifXmlm.peekinput=`El_endthenempty_taginputtagelsefinputtaginclose_tagtaginput;rletmap_tagtagfinput=lett=open_taginputinift=tagthen(letr=finputinclose_tagtaginput;r)elseparse_error(sprintf"open_tag(%s)"t)(sprintf"open_tag(%s)"tag)inputletnameinput=map_tag"name"get_datainputletdatafinput=map_tag"data"finputletvaluefinput=lett=open_taginputinift="value"then(letr=matchXmlm.peekinputwith|`El_end->Rpc.String""|`Datad->let_=Xmlm.inputinputinifis_emptyd&&matchXmlm.peekinputwith|`El_start_->true|_->falsethenfinputelseRpc.Stringd|_->finputinclose_tag"value"input;r)elseparse_error"open_tag(value)"(sprintf"open_tag(%s)"t)inputletmembersfinput=letginput=letname=nameinputinletvalue=fnameinputinname,valueinletr=ref[]inskip_emptyinput;whileXmlm.peekinput<>`El_enddor:=map_tag"member"ginput::!r;skip_emptyinputdone;List.rev!r(* Constructors *)letmakefn?callbackaccudata=letr=fndatainmatchcallbackwith|Somef->f(List.revaccu)r;r|None->rletmake_null=make(fun()->Null)letmake_int=make(fundata->Int(Int64.of_stringdata))letmake_bool=make(fundata->Bool(ifdata="1"thentrueelsefalse))letmake_float=make(fundata->Float(float_of_stringdata))letmake_string=make(fundata->Stringdata)letmake_dateTime=make(fundata->DateTimedata)letmake_base64?(base64_decoder=funs->Base64.decode_exns)=make(fundata->Base64(base64_decoderdata))letmake_enum=make(fundata->Enumdata)letmake_dict=make(fundata->Dictdata)(* General parser functions *)letrecof_xml?callback?base64_decoderaccuinput=tryvalue(map_tags(basic_types?callback?base64_decoderaccu))inputwith|Xmlm.Error((a,b),e)asexn->eprintf"Characters %i--%i: %s\n%!"ab(Xmlm.error_messagee);raiseexn|e->eprintf"%s\n%!"(Printexc.to_stringe);raiseeandbasic_types?callback?base64_decoderaccuinput=function|"int"|"i8"|"i4"->make_int?callbackaccu(get_datainput)|"boolean"->make_bool?callbackaccu(get_datainput)|"double"->make_float?callbackaccu(get_datainput)|"string"->make_string?callbackaccu(get_datainput)|"dateTime.iso8601"->make_dateTime?callbackaccu(get_datainput)|"base64"->make_base64?callback?base64_decoderaccu(get_datainput)|"array"->make_enum?callbackaccu(data(of_xmls?callbackaccu)input)|"struct"->make_dict?callbackaccu(members(funname->of_xml?callback(name::accu))input)|"nil"->make_null?callbackaccu()|tag->parse_error(sprintf"open_tag(%s)"tag)"open_tag(int/i8/i4/boolean/double/string/dateTime.iso8601/array/struct/nil)"inputandof_xmls?callbackaccuinput=letr=ref[]inskip_emptyinput;whileXmlm.peekinput<>`El_enddor:=of_xml?callbackaccuinput::!r;skip_emptyinputdone;List.rev!rendletof_string?callback?base64_decoderstr=letinput=Xmlm.make_input(`String(0,str))in(matchXmlm.peekinputwith|`Dtd_->ignore(Xmlm.inputinput)|_->());Parser.of_xml?callback?base64_decoder[]inputletof_a?callback?base64_decoder~next_charb=letaux()=matchnext_charbwith|Somec->int_of_charc|None->raiseEnd_of_fileinletinput=Xmlm.make_input(`Funaux)inParser.of_xml?callback?base64_decoder[]inputletcall_of_string?callback?base64_decoderstr=letinput=Xmlm.make_input(`String(0,str))in(matchXmlm.peekinputwith|`Dtd_->ignore(Xmlm.inputinput)|_->());letname=ref""inletparams=ref[]inParser.map_tag"methodCall"(funinput->name:=Parser.map_tag"methodName"Parser.get_datainput;Parser.map_tag"params"(funinput->Parser.skip_emptyinput;whileXmlm.peekinput<>`El_enddoParser.map_tag"param"(funinput->params:=Parser.of_xml?callback?base64_decoder[]input::!params)input;Parser.skip_emptyinputdone)input)input;call!name(List.rev!params)letresponse_of_fault?callback?base64_decoderinput=Parser.map_tag"fault"(funinput->matchParser.of_xml?callback?base64_decoder[]inputwith|Dictd->letfault_code=List.assoc"faultCode"dinletfault_string=List.assoc"faultString"dinfailure(Rpc.Enum[String"fault";fault_code;fault_string])|r->parse_error(to_stringr)"fault"input)inputletresponse_of_success?callback?base64_decoderinput=Parser.map_tag"params"(funinput->Parser.map_tag"param"(funinput->matchParser.of_xml?callback?base64_decoder[]inputwith|Dictd->ifList.mem_assoc"Status"d&&List.assoc"Status"d=String"Success"&&List.mem_assoc"Value"dthensuccess(List.assoc"Value"d)elseifList.mem_assoc"Status"d&&List.assoc"Status"d=String"Failure"&&List.mem_assoc"ErrorDescription"dthenfailure(List.assoc"ErrorDescription"d)elsesuccess(Dictd)|v->successv)input)inputletresponse_of_input?callback?base64_decoderinput=(matchXmlm.peekinputwith|`Dtd_->ignore(Xmlm.inputinput)|_->());Parser.map_tag"methodResponse"(funinput->Parser.skip_emptyinput;matchXmlm.peekinputwith|`El_start((_,"params"),_)->response_of_success?callback?base64_decoderinput|`El_start((_,"fault"),_)->response_of_fault?callback?base64_decoderinput|`El_start((_,tag),_)->parse_error(sprintf"open_tag(%s)"tag)"open_tag(fault/params)"input|`Datad->parse_error(String.escapedd)"open_tag(fault/params)"input|`El_end->parse_error"close_tag""open_tag(fault/params)"input|`Dtd_->parse_error"dtd""open_tag(fault/params)"input)inputletresponse_of_string?callback?base64_decoderstr=letinput=Xmlm.make_input(`String(0,str))inresponse_of_input?callback?base64_decoderinputletresponse_of_in_channel?callback?base64_decoderchan=letinput=Xmlm.make_input(`Channelchan)inresponse_of_input?callback?base64_decoderinput