123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896(*
* Copyright (c) 2012-2014 Anil Madhavapeddy <anil@recoil.org>
* Copyright (c) 2012-2014 David Sheets <sheets@alum.mit.edu>
*
* 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.
*
*)[@@@ocaml.warning"-32"]typecomponent=[|`Scheme|`Authority|`Userinfo(* subcomponent of authority in some schemes *)|`Host(* subcomponent of authority in some schemes *)|`Path|`Query|`Query_key|`Query_value|`Fragment]letreciter_concatfnsepbuf=function|last::[]->fnbuflast|el::rest->fnbufel;Buffer.add_stringbufsep;iter_concatfnsepbufrest|[]->()letrev_interjectelst=letrecauxacc=function|[]->acc|x::xs->aux(x::e::acc)xsinmatchlstwith|[]->[]|h::t->aux[h]tletcompare_optctt'=matcht,t'with|None,None->0|Some_,None->1|None,Some_->-1|Somea,Someb->cabletreccompare_listftt'=matcht,t'with|[],[]->0|_::_,[]->1|[],_::_->-1|x::xs,y::ys->matchfxywith0->compare_listfxsys|c->c(** Safe characters that are always allowed in a URI
* Unfortunately, this varies depending on which bit of the URI
* is being parsed, so there are multiple variants (and this
* set is probably not exhaustive. TODO: check.
*)typesafe_chars=boolarraymoduletypeScheme=sigvalsafe_chars_for_component:component->safe_charsvalnormalize_host:stringoption->stringoptionvalcanonicalize_port:intoption->intoptionvalcanonicalize_path:stringlist->stringlistendmoduleGeneric:Scheme=structletsub_delimsa=letsubd="!$&'()*+,;="infori=0toString.lengthsubd-1doletc=Char.codesubd.[i]ina.(c)<-truedone;aletsafe_chars:safe_chars=leta=Array.make256falseinletalways_safe="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-~"infori=0toString.lengthalways_safe-1doletc=Char.codealways_safe.[i]ina.(c)<-truedone;aletpchar:safe_chars=leta=sub_delims(Array.copysafe_chars)ina.(Char.code':')<-true;a.(Char.code'@')<-true;aletsafe_chars_for_scheme:safe_chars=leta=Array.copysafe_charsina.(Char.code'+')<-true;a(** Safe characters for the path component of a URI *)letsafe_chars_for_path:safe_chars=leta=sub_delims(Array.copypchar)in(* delimiter: non-segment delimiting uses should be pct encoded *)a.(Char.code'/')<-false;aletsafe_chars_for_query:safe_chars=(* TODO: What about {"!","$",","}? See <https://github.com/avsm/ocaml-uri/commit/1ef3f1dfb41bdb4f33f223ffe16e62a33975661a#diff-740f2de53c9eb36e9670ddfbdb9ba914R171> *)leta=Array.copypcharina.(Char.code'/')<-true;a.(Char.code'?')<-true;(* '&' is safe but we should encode literals to avoid ambiguity
with the already parsed qs params *)a.(Char.code'&')<-false;(* ';' is safe but some systems treat it like '&'. *)a.(Char.code';')<-false;a.(Char.code'+')<-false;aletsafe_chars_for_query_key:safe_chars=leta=Array.copysafe_chars_for_queryina.(Char.code'=')<-false;aletsafe_chars_for_query_value:safe_chars=leta=Array.copysafe_chars_for_queryina.(Char.code',')<-false;aletsafe_chars_for_fragment:safe_chars=safe_chars_for_query(** Safe characters for the userinfo subcomponent of a URI.
TODO: this needs more reserved characters added *)letsafe_chars_for_userinfo:safe_chars=leta=Array.copysafe_charsin(* delimiter: non-segment delimiting uses should be pct encoded *)a.(Char.code':')<-false;aletsafe_chars_for_component=function|`Path->safe_chars_for_path|`Userinfo->safe_chars_for_userinfo|`Query->safe_chars_for_query|`Query_key->safe_chars_for_query_key|`Query_value->safe_chars_for_query_value|`Fragment->safe_chars_for_fragment|`Scheme->safe_chars_for_scheme|_->safe_charsletnormalize_hosthso=hsoletcanonicalize_portport=portletcanonicalize_pathpath=pathendmoduleHttp:Scheme=structincludeGenericletnormalize_host=function|Somehs->Some(String.lowercase_asciihs)|None->Noneletcanonicalize_port=function|None->None|Some80->None|Somex->Somexletcanonicalize_path=function|[]->["/"]|x->xendmoduleHttps:Scheme=structincludeHttpletcanonicalize_port=function|None->None|Some443->None|Somex->SomexendmoduleFile:Scheme=structincludeGenericletnormalize_host=function|Somehs->leths=String.lowercase_asciihsinifhs="localhost"thenSome""elseSomehs|None->NoneendmoduleUrn:Scheme=structincludeGenericendletmodule_of_scheme=function|Somes->beginmatchString.lowercase_asciiswith|"http"->(moduleHttp:Scheme)|"https"->(moduleHttps:Scheme)|"file"->(moduleFile:Scheme)|"urn"->(moduleUrn:Scheme)|_->(moduleGeneric:Scheme)end|None->(moduleGeneric:Scheme)(** Portions of the URL must be converted to-and-from percent-encoding
* and this really, really shouldn't be mixed up. So this Pct module
* defines abstract Pct.encoded and Pct.decoded types which sets the
* state of the underlying string. There are functions to "cast" to
* and from these and normal strings, and this promotes a bit of
* internal safety. These types are not exposed to the external
* interface, as casting to-and-from is quite a bit of hassle and
* probably not a lot of use to the average consumer of this library
*)modulePct:sigtypeencodedtypedecodedvalencode:?scheme:string->?component:component->decoded->encodedvaldecode:encoded->decoded(* The empty decoded string *)valempty_decoded:decoded(* Identity functions so we need to explicitly cast when using them below *)valcast_encoded:string->encodedvalcast_decoded:string->decodedvaluncast_encoded:encoded->stringvaluncast_decoded:decoded->string(* Lift HOFs for maps over encodings, decodings, and strings *)vallift_encoded:(encoded->encoded)->string->stringvallift_decoded:(decoded->decoded)->string->stringvalunlift_encoded:(string->string)->encoded->encodedvalunlift_decoded:(string->string)->decoded->decodedvalunlift_decoded2:(string->string->'a)->decoded->decoded->'aend=structtypeencoded=stringtypedecoded=stringletcast_encodedx=xletcast_decodedx=xletempty_decoded=""letuncast_decodedx=xletuncast_encodedx=xletlift_encodedf=fletlift_decodedf=fletunlift_encodedf=fletunlift_decodedf=fletunlift_decoded2f=f(** Scan for reserved characters and replace them with
percent-encoded equivalents.
@return a percent-encoded string *)letencode?scheme?(component=`Path)b=letmoduleScheme=(val(module_of_schemescheme):Scheme)inletsafe_chars=Scheme.safe_chars_for_componentcomponentinletlen=String.lengthbinletbuf=Buffer.createleninletrecscanstartcur=ifcur>=lenthenbeginBuffer.add_substringbufbstart(cur-start);endelsebeginletc=Char.codeb.[cur]inifsafe_chars.(c)thenscanstart(cur+1)elsebeginifcur>startthenBuffer.add_substringbufbstart(cur-start);Buffer.add_stringbuf(Printf.sprintf"%%%02X"c);scan(cur+1)(cur+1)endendinscan00;Buffer.contentsbufletint_of_hex_charc=letc=int_of_char(Char.uppercase_asciic)-48inifc>9thenifc>16&&c<23thenc-7elsefailwith"int_of_hex_char"elseifc>=0thencelsefailwith"int_of_hex_char"(** Scan for percent-encoding and convert them into ASCII.
@return a percent-decoded string *)letdecodeb=(* TODO: Should both strict and non-strict versions be exposed? *)letlen=String.lengthbinletbuf=Buffer.createleninletrecscanstartcur=ifcur>=lenthenBuffer.add_substringbufbstart(cur-start)elseifb.[cur]='%'thenbeginBuffer.add_substringbufbstart(cur-start);letcur=cur+1inifcur>=lenthenBuffer.add_charbuf'%'elsematchint_of_hex_charb.[cur]with|exception_->Buffer.add_charbuf'%';scancurcur|highbits->beginletcur=cur+1inifcur>=lenthenbeginBuffer.add_charbuf'%';Buffer.add_charbufb.[cur-1]endelsebeginletstart_at=matchint_of_hex_charb.[cur]with|lowbits->Buffer.add_charbuf(Char.chr(highbitslsl4+lowbits));cur+1|exception_->Buffer.add_charbuf'%';Buffer.add_charbufb.[cur-1];curinscanstart_atstart_atendendendelsescanstart(cur+1)inscan00;Buffer.contentsbufend(* Percent encode a string *)letpct_encode?scheme?(component=`Path)s=Pct.(uncast_encoded(encode?scheme~component(cast_decodeds)))(* Percent decode a string *)letpct_decodes=Pct.(uncast_decoded(decode(cast_encodeds)))(* Userinfo string handling, to and from an id * credential pair *)moduleUserinfo=structtypet=string*stringoptionletcompare(u,p)(u',p')=matchString.compareuu'with|0->compare_optString.comparepp'|c->cletuserinfo_of_encodedus=matchStringext.split~max:2~on:':'uswith|[]->("",None)|[u]->(pct_decodeu,None)|u::p::_->(pct_decodeu,Some(pct_decodep))letencoded_of_userinfo?scheme(u,po)=letlen=String.(1+(lengthu)+(matchpowithNone->0|Somep->lengthp))inletbuf=Buffer.createleninBuffer.add_stringbuf(pct_encode?scheme~component:`Userinfou);beginmatchpowithNone->();|Somep->Buffer.add_charbuf':';Buffer.add_stringbuf(pct_encode?scheme~component:`Userinfop)end;Pct.cast_encoded(Buffer.contentsbuf)endletuserinfo_of_encoded=Userinfo.userinfo_of_encodedletencoded_of_userinfo?scheme=Userinfo.encoded_of_userinfo?scheme(* Path string handling, to and from a list of path tokens *)modulePath=struct(* Invariant: every element is non-zero, slashes (/) only occur alone. *)(* Yes, it's better this way. This means you can retain separator
context in recursion (e.g. remove_dot_segments for relative resolution). *)typet=stringlistletcompare=compare_listString.compare(* Make a path token list from a percent-encoded string *)letpath_of_encodedps=lettokl=Stringext.full_splitps~on:'/'inList.mappct_decodetokl(* Subroutine for resolve <http://tools.ietf.org/html/rfc3986#section-5.2.4> *)letremove_dot_segmentsp=letrevp=List.revpinletrecloopascensionoutp=function|"/"::".."::r|".."::r->loop(ascension+1)outpr|"/"::"."::r|"."::r->loopascensionoutpr|"/"::[]|[]whenList.(lengthp>0&&hdp="/")->"/"::outp|[]whenascension>0->List.rev_append("/"::(rev_interject"/"Array.(to_list(makeascension".."))))outp|[]->List.(iflengthoutp>0&&hdoutp="/"thentloutpelseoutp)|"/"::"/"::rwhenascension>0->loop(ascension-1)outp("/"::r)|"/"::_::rwhenascension>0->loop(ascension-1)outpr|s::r->loop0(s::outp)rinloop0[]revpletencoded_of_path?schemep=letlen=List.fold_left(functok->String.lengthtok+c)0pinletbuf=Buffer.createleniniter_concat(funbuf->function|"/"->Buffer.add_charbuf'/'|seg->Buffer.add_stringbuf(pct_encode?scheme~component:`Pathseg))""bufp;Pct.cast_encoded(Buffer.contentsbuf)(* Subroutine for resolve <http://tools.ietf.org/html/rfc3986#section-5.2.3> *)letmergebhostbpathrelpath=matchbhost,List.revbpathwith|Some_,[]->"/"::relpath|_,("/"::rbpath|_::"/"::rbpath)->List.rev_append("/"::rbpath)relpath|_,_->relpathendletpath_of_encoded=Path.path_of_encodedletencoded_of_path?scheme=Path.encoded_of_path?scheme(* Query string handling, to and from an assoc list of key/values *)moduleQuery=structtypekv=(string*stringlist)listtypet=|KVofkv|Rawofstringoption*kvLazy.tletcomparexy=matchx,ywith|KVkvl,KVkvl'|Raw(_,lazykvl),KVkvl'|KVkvl,Raw(_,lazykvl')->compare_list(fun(k,vl)(k',vl')->matchString.comparekk'with|0->compare_listString.comparevlvl'|c->c)kvlkvl'|Raw(raw,_),Raw(raw',_)->compare_optString.comparerawraw'letfindqk=trySome(List.assockq)withNot_found->Noneletsplit_queryqs=letels=Stringext.split~on:'&'qsin(* Replace a + in a query string with a space in-place *)letplus_to_spaces=lets=Bytes.unsafe_of_stringsinfori=0toBytes.lengths-1doifBytes.getsi='+'thenBytes.setsi' 'done;Bytes.unsafe_to_stringsinletrecloopacc=function|(k::v::_)::tl->letn=plus_to_spacek,(matchStringext.split~on:','(plus_to_spacev)with|[]->[""]|l->l)inloop(n::acc)tl|[k]::tl->letn=plus_to_spacek,[]inloop(n::acc)tl|[]::tl->loop(("",[])::acc)tl|[]->accinmatchelswith|[]->["",[]]|els->loop[](List.rev_map(funel->Stringext.split~on:'='el~max:2)els)(* Make a query tuple list from a percent-encoded string *)letquery_of_encodedqs=List.map(fun(k,v)->(pct_decodek,List.mappct_decodev))(split_queryqs)(* Assemble a query string suitable for putting into a URI.
* Tuple inputs are percent decoded and will be encoded by
* this function.
*)letencoded_of_query?schemel=letlen=List.fold_left(funa(k,v)->a+(String.lengthk)+(List.fold_left(funas->a+(String.lengths)+1)0v)+2)(-1)linletbuf=Buffer.createleniniter_concat(funbuf(k,v)->Buffer.add_stringbuf(pct_encode?scheme~component:`Query_keyk);ifv<>[]then(Buffer.add_charbuf'=';iter_concat(funbufs->Buffer.add_stringbuf(pct_encode?scheme~component:`Query_values))","bufv))"&"bufl;Buffer.contentsbufletof_rawqs=letlazy_query=Lazy.from_fun(fun()->query_of_encodedqs)inRaw(Someqs,lazy_query)letkv=functionRaw(_,lazykv)|KVkv->kvendletquery_of_encoded=Query.query_of_encodedletencoded_of_query?scheme=Query.encoded_of_query?scheme(* Type of the URI, with most bits being optional *)typet={scheme:Pct.decodedoption;userinfo:Userinfo.toption;host:Pct.decodedoption;port:intoption;path:Path.t;query:Query.t;fragment:Pct.decodedoption;}letempty={scheme=None;userinfo=None;host=None;port=None;path=[];query=Query.Raw(None,Lazy.from_val[]);fragment=None;}letcompare_decoded=Pct.unlift_decoded2String.compareletcompare_decoded_opt=compare_optcompare_decodedletcomparett'=(matchcompare_decoded_optt.hostt'.hostwith|0->(matchcompare_decoded_optt.schemet'.schemewith|0->(matchcompare_opt(funpp'->ifp<p'then-1elseifp>p'then1else0)t.portt'.portwith|0->(matchcompare_optUserinfo.comparet.userinfot'.userinfowith|0->(matchPath.comparet.patht'.pathwith|0->(matchQuery.comparet.queryt'.querywith|0->compare_decoded_optt.fragmentt'.fragment|c->c)|c->c)|c->c)|c->c)|c->c)|c->c)letequaltt'=comparett'=0letuncast_opt=function|Someh->Some(Pct.uncast_decodedh)|None->Noneletcast_opt=function|Someh->Some(Pct.cast_decodedh)|None->Noneletnormalizeschemuri=letmoduleScheme=(val(module_of_scheme(uncast_optschem)):Scheme)inletdobf=function|Somex->Some(Pct.unlift_decodedfx)|None->Nonein{uriwithscheme=dobString.lowercase_asciiuri.scheme;host=cast_opt(Scheme.normalize_host(uncast_opturi.host))}(* Make a URI record. This is a bit more inefficient than it needs to be due to the
* casting/uncasting (which isn't fully identity due to the option box), but it is
* no big deal for now.
*)letmake?scheme?userinfo?host?port?path?query?fragment()=letdecode=function|Somex->Some(Pct.cast_decodedx)|None->Noneinlethost=matchuserinfo,host,portwith|_,Some_,_|None,None,None->host|Some_,None,_|_,None,Some_->Some""inletuserinfo=matchuserinfowith|None->None|Someu->Some(userinfo_of_encodedu)inletpath=matchpathwith|None->[]|Somep->letpath=path_of_encodedpinmatchhost,pathwith|None,_|Some_,"/"::_|Some_,[]->path|Some_,_->"/"::pathinletquery=matchquerywith|None->Query.KV[]|Somep->Query.KVpinletscheme=decodeschemeinnormalizescheme{scheme;userinfo;host=decodehost;port;path;query;fragment=decodefragment}(** Parse a URI string into a structure *)letof_strings=(* Given a series of Re substrings, cast each component
* into a Pct.encoded and return an optional type (None if
* the component is not present in the Uri *)letget_opt_encodedsn=trySome(Pct.cast_encoded(Re.Group.getsn))withNot_found->Noneinletget_optsn=tryletpct=Pct.cast_encoded(Re.Group.getsn)inSome(Pct.decodepct)withNot_found->Noneinletsubs=Re.execUri_re.uri_referencesinletscheme=get_optsubs2inletuserinfo,host,port=matchget_opt_encodedsubs4with|None->None,None,None|Somea->letsubs'=Re.execUri_re.authority(Pct.uncast_encodeda)inletuserinfo=matchget_opt_encodedsubs'1with|Somex->Some(Userinfo.userinfo_of_encoded(Pct.uncast_encodedx))|None->Noneinlethost=get_optsubs'2inletport=matchget_optsubs'3with|None->None|Somex->(trySome(int_of_string(Pct.uncast_decodedx))with_->None)inuserinfo,host,portinletpath=matchget_opt_encodedsubs5with|Somex->Path.path_of_encoded(Pct.uncast_encodedx)|None->[]inletquery=matchget_opt_encodedsubs7with|Somex->Query.of_raw(Pct.uncast_encodedx)|None->Query.Raw(None,Lazy.from_val[])inletfragment=get_optsubs9innormalizescheme{scheme;userinfo;host;port;path;query;fragment}(** Convert a URI structure into a percent-encoded string
<http://tools.ietf.org/html/rfc3986#section-5.3>
*)letto_stringuri=letscheme=matchuri.schemewith|Somes->Some(Pct.uncast_decodeds)|None->Noneinletbuf=Buffer.create128in(* Percent encode a decoded string and add it to the buffer *)letadd_pct_string?(component=`Path)x=Buffer.add_stringbuf(Pct.uncast_encoded(Pct.encode?scheme~componentx))in(matchuri.schemewith|None->()|Somex->add_pct_string~component:`Schemex;Buffer.add_charbuf':');(* URI has a host if any host-related component is set. Defaults to "". *)if(matchuri.userinfo,uri.host,uri.portwith|Some_,_,_|_,Some_,_|_,_,Some_->true|_->false)thenBuffer.add_stringbuf"//";(matchuri.userinfowith|None->()|Someuserinfo->Buffer.add_stringbuf(Pct.uncast_encoded(encoded_of_userinfo?schemeuserinfo));Buffer.add_charbuf'@');(matchuri.hostwith|None->()|Somehost->add_pct_string~component:`Hosthost;);(matchuri.portwith|None->()|Someport->Buffer.add_charbuf':';Buffer.add_stringbuf(string_of_intport));(matchuri.pathwith(* Handle relative paths correctly *)|[]->()|"/"::_->Buffer.add_stringbuf(Pct.uncast_encoded(encoded_of_path?schemeuri.path))|first_segment::_->(matchuri.hostwith|Some_->Buffer.add_charbuf'/'|None->(* ensure roundtrip by forcing relative path interpretation not scheme *)matchStringext.find_fromfirst_segment~pattern:":"with|None->()|Some_->matchschemewith|Some_->()|None->Buffer.add_stringbuf"./");Buffer.add_stringbuf(Pct.uncast_encoded(encoded_of_path?schemeuri.path)));Query.(matchuri.querywith|Raw(None,_)|KV[]->()|Raw(_,lazyq)|KVq->(* normalize e.g. percent capitalization *)Buffer.add_charbuf'?';Buffer.add_stringbuf(encoded_of_query?schemeq));(matchuri.fragmentwith|None->()|Somef->Buffer.add_charbuf'#';add_pct_string~component:`Fragmentf);Buffer.contentsbuf(* Various accessor functions, as the external uri type is abstract *)letget_decoded_opt=functionNone->None|Somex->Some(Pct.uncast_decodedx)letschemeuri=get_decoded_opturi.schemeletwith_schemeuri=function|Somescheme->{uriwithscheme=Some(Pct.cast_decodedscheme)}|None->{uriwithscheme=None}lethosturi=get_decoded_opturi.hostletwith_hosturi=function|Somehost->{uriwithhost=Some(Pct.cast_decodedhost)}|None->{uriwithhost=None}lethost_with_default?(default="localhost")uri=matchhosturiwith|None->default|Someh->hletuserinfouri=matchuri.userinfowith|None->None|Someuserinfo->Some(Pct.uncast_encoded(matchuri.schemewith|None->encoded_of_userinfouserinfo|Somes->encoded_of_userinfo~scheme:(Pct.uncast_decodeds)userinfo))letwith_userinfouriuserinfo=letuserinfo=matchuserinfowith|Someu->Some(userinfo_of_encodedu)|None->Noneinmatchhosturiwith|None->{uriwithhost=Some(Pct.cast_decoded"");userinfo=userinfo}|Some_->{uriwithuserinfo=userinfo}letuseruri=matchuri.userinfowith|None->None|Some(user,_)->Someuserletpassworduri=matchuri.userinfowith|None|Some(_,None)->None|Some(_,Somepass)->Somepassletwith_passworduripassword=letresultuserinfo=matchhosturiwith|None->{uriwithhost=Some(Pct.cast_decoded"");userinfo=userinfo}|Some_->{uriwithuserinfo=userinfo}inmatchuri.userinfo,passwordwith|None,None->uri|None,Some_->result(Some("",password))|Some(user,_),_->result(Some(user,password))letporturi=uri.portletwith_porturiport=matchhosturiwith|Some_->{uriwithport=port}|None->beginmatchportwith|None->{uriwithhost=None;port=None}|Some_->{uriwithhost=Some(Pct.cast_decoded"");port=port}end(* Return the path component *)letpathuri=Pct.uncast_encoded(matchuri.schemewith|None->encoded_of_pathuri.path|Somes->encoded_of_path~scheme:(Pct.uncast_decodeds)uri.path)letwith_pathuripath=letpath=path_of_encodedpathinmatchhosturi,pathwith|None,_|Some_,"/"::_|Some_,[]->{uriwithpath=path}|Some_,_->{uriwithpath="/"::path}letfragmenturi=get_decoded_opturi.fragmentletwith_fragmenturi=function|None->{uriwithfragment=None}|Somefrag->{uriwithfragment=Some(Pct.cast_decodedfrag)}letqueryuri=Query.kvuri.queryletverbatim_queryuri=Query.(matchuri.querywith|Raw(qs,_)->qs|KV[]->None|KVkv->Some(encoded_of_query?scheme:(schemeuri)kv))letget_query_param'urik=Query.(find(kvuri.query)k)letget_query_paramurik=matchget_query_param'urikwith|None->None|Somev->Some(String.concat","v)letwith_queryuriquery={uriwithquery=Query.KVquery}letq_sq=List.map(fun(k,v)->k,[v])qletwith_query'uriquery=with_queryuri(q_squery)letadd_query_paramurip=Query.({uriwithquery=KV(p::(kvuri.query))})letadd_query_param'uri(k,v)=Query.({uriwithquery=KV((k,[v])::(kvuri.query))})letadd_query_paramsurips=Query.({uriwithquery=KV(ps@(kvuri.query))})letadd_query_params'urips=Query.({uriwithquery=KV((q_sps)@(kvuri.query))})letremove_query_paramurik=Query.({uriwithquery=KV(List.filter(fun(k',_)->k<>k')(kvuri.query))})letwith_uri?scheme?userinfo?host?port?path?query?fragmenturi=letwith_path_optuo=matchowith|None->with_pathu""|Somep->with_pathupinletwith_query_optuo=matchowith|None->with_queryu[]|Someq->with_queryuqinletwith_fou=matchowith|None->u|Somex->fuxinwith_with_schemeschemeuri|>with_with_userinfouserinfo|>with_with_hosthost|>with_with_portport|>with_with_path_optpath|>with_with_query_optquery|>with_with_fragmentfragment(* Construct encoded path and query components *)letpath_and_queryuri=match(pathuri),(queryuri)with|"",[]->"/"(* TODO: What about same document? (/) *)|"",q->(* TODO: What about same document? (/) *)letscheme=uncast_opturi.schemeinPrintf.sprintf"/?%s"(encoded_of_query?schemeq)|p,[]->p|p,q->letscheme=uncast_opturi.schemeinPrintf.sprintf"%s?%s"p(encoded_of_query?schemeq)(* TODO: functions to add and remove from a URI *)(* Resolve a URI wrt a base URI <http://tools.ietf.org/html/rfc3986#section-5.2> *)letresolveschembaseuri=letschem=Some(Pct.cast_decoded(matchschemebasewith|None->schem|Somescheme->scheme))innormalizeschemPath.(matchschemeuri,userinfouri,hosturiwith|Some_,_,_->{uriwithpath=remove_dot_segmentsuri.path}|None,Some_,_|None,_,Some_->{uriwithscheme=base.scheme;path=remove_dot_segmentsuri.path}|None,None,None->leturi={uriwithscheme=base.scheme;userinfo=base.userinfo;host=base.host;port=base.port}inletpath_str=pathuriinifpath_str=""then{uriwithpath=base.path;query=matchuri.querywith|Query.Raw(None,_)|Query.KV[]->base.query|_->uri.query}elseifpath_str.[0]='/'then{uriwithpath=remove_dot_segmentsuri.path}else{uriwithpath=remove_dot_segments(mergebase.hostbase.pathuri.path);})letcanonicalizeuri=leturi=resolve""emptyuriinletmoduleScheme=(val(module_of_scheme(uncast_opturi.scheme)):Scheme)in{uriwithport=Scheme.canonicalize_porturi.port;path=Scheme.canonicalize_pathuri.path;}letppppfuri=Format.pp_print_stringppf(to_stringuri)letpp_humppfuri=Format.pp_print_stringppf(to_stringuri)