12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844(* $Id$
* ----------------------------------------------------------------------
*
*)openPrintfexceptionMalformed_URLtypeurl_syntax_option=Url_part_not_recognized|Url_part_allowed|Url_part_requiredtypeurl_syntax={url_enable_scheme:url_syntax_option;url_enable_user:url_syntax_option;url_enable_user_param:url_syntax_option;url_enable_password:url_syntax_option;url_enable_host:url_syntax_option;url_enable_port:url_syntax_option;url_enable_path:url_syntax_option;url_enable_param:url_syntax_option;url_enable_query:url_syntax_option;url_enable_fragment:url_syntax_option;url_enable_other:url_syntax_option;url_accepts_8bits:bool;url_is_valid:url->bool;url_enable_relative:bool;}andurl={url_syntax:url_syntax;mutableurl_validity:bool;url_scheme:stringoption;url_user:stringoption;url_user_param:stringlist;url_password:stringoption;url_host:stringoption;url_port:intoption;url_path:stringlist;url_param:string list;url_query:string option;url_fragment:stringoption;url_other:stringoption;};;typechar_category=Accepted|Rejected|Separatorletscan_url_partsk_fromk_tocatsaccept_8bits=(*Scans thelongest word of accepted characters from position 'k_from'
* in 's' until at most position 'k_to'. The character following the
* word (if any) must be a separator character.
* On success, the function returns the position of the last character
* of the word + 1.
* If there is any rejected character before the separator or the end
* of the string (i.e. position 'k_to') is reached, the exception
* Malformed_URL is raised.
* Furthermore, if the character '%' is accepted it is checked whether
* two hexadecimal digits follow (which must be accepted, too). If this
* is not true, the exception Malformed_URL is raised, too.
* 'cats': contains for every character code (0 to 255) the category
* of the character.
*)letcheck_hexc=ifcats.(Char.codec)<>AcceptedthenraiseMalformed_URL;matchcwith('0'..'9'|'A'..'F'|'a'..'f')->()|_->raiseMalformed_URLinletrecscank=ifk>=k_tothenkelsebeginletc=s.[k]inletcat=cats.(Char.codec)inmatchcatwithAccepted->ifc='%'thenbeginifk+2>=k_tothenraiseMalformed_URL;letc1=s.[k+1]inletc2=s.[k+2]incheck_hexc1;check_hexc2;scan(k+3)endelsescan(k+1)|Separator->k|Rejected ->ifaccept_8bits&&c>='\128'thenscan(k+1)elseraiseMalformed_URLendinassert(Array.lengthcats=256);assert(k_from>=0);assert(k_from<=k_to);assert(k_to<=String.lengths);scank_from;;(*Create a categorization: *)letlalpha=['a';'b';'c';'d';'e';'f';'g';'h';'i';'j';'k';'l';'m';'n';'o';'p';'q';'r';'s';'t';'u';'v';'w';'x';'y';'z']letualpha=['A';'B';'C';'D';'E';'F';'G';'H';'I';'J';'K';'L';'M';'N';'O';'P';'Q';'R';'S';'T';'U';'V';'W';'X';'Y';'Z']letdigit=['0';'1';'2';'3';'4';'5';'6';'7';'8';'9']lethex_digit=['a';'b';'c';'d';'e';'f';'A';'B';'C';'D';'E';'F';]@digitletsafe=['$';'-';'_';'.';'+']letextra=['!';'*';'\'';'(';')';',']letmake_catsacceptedseparators=(* create a categorization:
* - All characters listed in 'separators' are separators.
* - All characters listed in 'accepted' and which do not occur in
* 'separators' are accepted characters.
* - All other characters are rejected.
*)letcats=Array.make256RejectedinList.iter(func->cats.(Char.codec)<-Accepted)accepted;List.iter(func->cats.(Char.codec)<-Separator)separators;cats;;letscheme_cats=make_cats(lalpha@ualpha@['+';'-';'.'])[':'];;(* scheme_cats: character categorization to _extract_ the URL scheme *)letlogin_cats=make_cats(lalpha@ualpha@digit@safe@extra@[';';'&';'=';'%'])[':';'@';'/';'#';'?'];;(* login_cats: character categorization to _extract_ user name, password,
* host name, and port.
*
* Note: user_params are extracted in a second step.
*
* Note: '?' is now a separator, as described in RFC 2396.
*)lethost_cats=make_cats(lalpha@ualpha@digit@['.';'-'])[];;(*host_cats: character categorization to _check_ whether the host name
* is formed only by legal characters.
* Especially '%' is not allowed here!
* IPv6 addresses are checked separately.
*)letipv6_cats=make_cats(hex_digit@[':'])[]letipv6_sep_cats=make_cats(hex_digit@[':'])[']']letport_cats=make_catsdigit[];;(* port_cats: character categorization to _check_ whether the port number
* is formed only by legal characters.
* Especially '%' is not allowed here!
*)letpath_catsseparators=make_cats(lalpha@ualpha @digit@safe@extra@['?';':';'@';'&';'=';';';'%';'/';'~'])separators;;letseparators_from_syntaxsyn=letinclude_ifsyn_optionclist=ifsyn_option <>Url_part_not_recognizedthenclistelse[]in(include_if syn.url_enable_param[';'])@(include_ifsyn.url_enable_query['?'])@(include_ifsyn.url_enable_fragment['#']);;letpath_cats_from_syntaxsynextraseps=letseparators=separators_from_syntax syninpath_cats(separators@extraseps);;(* path_cats_from_syntax:
* Computes a character categorization to extract the path from an URL.
* This depends on the syntax because the list of possible separators
* contains the characters that may begin the next URL clause.
*
* Notes:
* - The '#' is rejected unless fragments are enabled.
* - The '~' is accepted although this violates RFC 1738 (but it is ok
* according to RFC 2396)
*)letother_cats_from_syntaxsyn=letinclude_ifsyn_optionclist=ifsyn_option <>Url_part_not_recognizedthenclistelse[]inletseparators=(include_ifsyn.url_enable_param[';'])@(include_ifsyn.url_enable_query['?'])@(include_ifsyn.url_enable_fragment['#'])inmake_cats(lalpha@ualpha@digit@safe@extra@(separators@['?';':';'@';'&';'=';';';'%';'/']))[];;(* other_cats: character categorization to extract or check the
* "other" part of the URL.
*)letextract_url_schemes=letl=String.lengthsinletk=scan_url_parts0lscheme_catsfalsein(* or raise Malformed_URL *)ifk=lthenraiseMalformed_URL;assert(s.[k]=':');STRING_LOWERCASE(String.subs0k);;let(=>)ab=nota||b;;(* implication *)let(<=>)(a:bool)b=(a=b);;(* equivalence *)leturl_syntax_is_validsyn=letrecognizedx=x<>Url_part_not_recognizedinlet _not_recognizedx=x=Url_part_not_recognizedin(recognizedsyn.url_enable_password=>recognizedsyn.url_enable_user)&&(recognizedsyn.url_enable_user_param=>recognizedsyn.url_enable_user)&&(recognizedsyn.url_enable_port=>recognizedsyn.url_enable_host)&&(recognizedsyn.url_enable_user=>recognizedsyn.url_enable_host)&¬((recognizedsyn.url_enable_user ||recognized syn.url_enable_password||recognizedsyn.url_enable_host||recognizedsyn.url_enable_port||recognizedsyn.url_enable_path)&&(recognizedsyn.url_enable_other));;letpartial_url_syntaxsyn=letweaken=functionUrl_part_not_recognized-> Url_part_not_recognized|Url_part_allowed->Url_part_allowed|Url_part_required->Url_part_allowedinifnotsyn.url_enable_relativethenfailwith"Neturl.partial_url_syntax: This syntax does not support relative URLs";{url_enable_scheme=weakensyn.url_enable_scheme;url_enable_user=weakensyn.url_enable_user;url_enable_user_param=weakensyn.url_enable_user_param;url_enable_password=weakensyn.url_enable_password;url_enable_host=weakensyn.url_enable_host;url_enable_port=weakensyn.url_enable_port;url_enable_path=weakensyn.url_enable_path;url_enable_param=weakensyn.url_enable_param;url_enable_query=weakensyn.url_enable_query;url_enable_fragment=weakensyn.url_enable_fragment;url_enable_other=weakensyn.url_enable_other;url_accepts_8bits=syn.url_accepts_8bits;url_is_valid=syn.url_is_valid;url_enable_relative =true;};;letfile_url_syntax={url_enable_scheme=Url_part_required;url_enable_user=Url_part_not_recognized;url_enable_user_param=Url_part_not_recognized;url_enable_password=Url_part_not_recognized;url_enable_host=Url_part_allowed;url_enable_port=Url_part_not_recognized;url_enable_path=Url_part_required;url_enable_param=Url_part_not_recognized;url_enable_query=Url_part_not_recognized;url_enable_fragment=Url_part_not_recognized;url_enable_other=Url_part_not_recognized;url_accepts_8bits=false;url_is_valid=(fun_->true);url_enable_relative=true;};;letftp_url_syntax={url_enable_scheme=Url_part_required;url_enable_user=Url_part_allowed;url_enable_user_param=Url_part_not_recognized;url_enable_password=Url_part_allowed;url_enable_host=Url_part_required;url_enable_port=Url_part_allowed;url_enable_path=Url_part_allowed;url_enable_param=Url_part_allowed;url_enable_query=Url_part_not_recognized;url_enable_fragment=Url_part_not_recognized;url_enable_other=Url_part_not_recognized;url_accepts_8bits=false;url_is_valid=(fun_->true);url_enable_relative=true;};;lethttp_url_syntax={url_enable_scheme=Url_part_required;url_enable_user=Url_part_allowed;url_enable_user_param=Url_part_not_recognized;url_enable_password=Url_part_allowed;url_enable_host=Url_part_required;url_enable_port=Url_part_allowed;url_enable_path=Url_part_allowed;url_enable_param=Url_part_not_recognized;url_enable_query=Url_part_allowed;url_enable_fragment=Url_part_not_recognized;url_enable_other=Url_part_not_recognized;url_accepts_8bits=false;url_is_valid=(fun_->true);url_enable_relative=true;};;letpop_url_syntax={url_enable_scheme=Url_part_required;url_enable_user=Url_part_allowed;url_enable_user_param=Url_part_allowed;url_enable_password=Url_part_allowed;url_enable_host=Url_part_required;url_enable_port=Url_part_allowed;url_enable_path=Url_part_not_recognized;url_enable_param=Url_part_not_recognized;url_enable_query=Url_part_not_recognized;url_enable_fragment=Url_part_not_recognized;url_enable_other=Url_part_not_recognized;url_accepts_8bits=false;url_is_valid=(fun_->true);url_enable_relative=false;};;letimap_url_syntax={url_enable_scheme=Url_part_required;url_enable_user=Url_part_allowed;url_enable_user_param=Url_part_allowed;url_enable_password=Url_part_allowed;url_enable_host=Url_part_required;url_enable_port=Url_part_allowed;url_enable_path=Url_part_allowed;url_enable_param=Url_part_not_recognized;url_enable_query=Url_part_allowed;url_enable_fragment=Url_part_not_recognized;url_enable_other=Url_part_not_recognized;url_accepts_8bits=false;url_is_valid=(fun_->true);url_enable_relative=true;};;letmailto_url_syntax={url_enable_scheme=Url_part_required;url_enable_user=Url_part_not_recognized;url_enable_user_param=Url_part_not_recognized;url_enable_password=Url_part_not_recognized;url_enable_host=Url_part_not_recognized;url_enable_port=Url_part_not_recognized;url_enable_path=Url_part_not_recognized;url_enable_param=Url_part_not_recognized;url_enable_query=Url_part_allowed;url_enable_fragment=Url_part_not_recognized;url_enable_other=Url_part_required;url_accepts_8bits=false;url_is_valid=(fun_->true);url_enable_relative=false;};;letnews_url_syntax={url_enable_scheme=Url_part_required;url_enable_user=Url_part_not_recognized;url_enable_user_param=Url_part_not_recognized;url_enable_password=Url_part_not_recognized;url_enable_host=Url_part_not_recognized;url_enable_port=Url_part_not_recognized;url_enable_path=Url_part_not_recognized;url_enable_param=Url_part_not_recognized;url_enable_query=Url_part_not_recognized;url_enable_fragment=Url_part_not_recognized;url_enable_other=Url_part_required;url_accepts_8bits=false;url_is_valid=(fun_->true);url_enable_relative=false;};;letcid_url_syntax=news_url_syntax;;letdata_url_syntax=news_url_syntax;;letnntp_url_syntax={url_enable_scheme=Url_part_required;url_enable_user=Url_part_not_recognized;url_enable_user_param=Url_part_not_recognized;url_enable_password=Url_part_not_recognized;url_enable_host=Url_part_required;url_enable_port=Url_part_allowed;url_enable_path=Url_part_required;url_enable_param=Url_part_not_recognized;url_enable_query=Url_part_not_recognized;url_enable_fragment=Url_part_not_recognized;url_enable_other=Url_part_not_recognized;url_accepts_8bits=false;url_is_valid=(funurl->List.lengthurl.url_path=3);url_enable_relative=true;};;letipp_url_syntax={url_enable_scheme=Url_part_required;url_enable_user=Url_part_not_recognized;url_enable_user_param=Url_part_not_recognized;url_enable_password=Url_part_not_recognized;url_enable_host=Url_part_required;url_enable_port=Url_part_allowed;url_enable_path=Url_part_allowed;url_enable_param=Url_part_not_recognized;url_enable_query=Url_part_allowed;url_enable_fragment=Url_part_not_recognized;url_enable_other=Url_part_not_recognized;url_accepts_8bits=false;url_is_valid=(fun_->true);url_enable_relative=true;};;letnull_url_syntax={url_enable_scheme=Url_part_not_recognized;url_enable_user=Url_part_not_recognized;url_enable_user_param=Url_part_not_recognized;url_enable_password=Url_part_not_recognized;url_enable_host=Url_part_not_recognized;url_enable_port=Url_part_not_recognized;url_enable_path=Url_part_not_recognized;url_enable_param=Url_part_not_recognized;url_enable_query=Url_part_not_recognized;url_enable_fragment=Url_part_not_recognized;url_enable_other=Url_part_not_recognized;url_accepts_8bits=false;url_is_valid=(fun_->true);url_enable_relative=false;};;letip_url_syntax={url_enable_scheme=Url_part_allowed;url_enable_user=Url_part_allowed;url_enable_user_param=Url_part_not_recognized;(* so user parameters are parsed as part
* of the user string! (Most generic.)
*)url_enable_password=Url_part_allowed;url_enable_host=Url_part_allowed;url_enable_port=Url_part_allowed;url_enable_path=Url_part_allowed;url_enable_param=Url_part_allowed;url_enable_query=Url_part_allowed;url_enable_fragment=Url_part_allowed;url_enable_other=Url_part_not_recognized;url_accepts_8bits=false;url_is_valid=(fun_->true);url_enable_relative=true;};;letldap_url_syntax={url_enable_scheme=Url_part_required;url_enable_user=Url_part_not_recognized;url_enable_user_param=Url_part_not_recognized;url_enable_password=Url_part_not_recognized;url_enable_host=Url_part_allowed;url_enable_port=Url_part_allowed;url_enable_path=Url_part_allowed;url_enable_param=Url_part_not_recognized;url_enable_query=Url_part_allowed;url_enable_fragment=Url_part_not_recognized;url_enable_other=Url_part_not_recognized;url_accepts_8bits=false;url_is_valid=(fun_->true);url_enable_relative=true;};;letcommon_url_syntax=leth=Hashtbl.create10inHashtbl.addh"file"file_url_syntax;Hashtbl.addh"ftp"ftp_url_syntax;Hashtbl.addh"http"http_url_syntax;Hashtbl.addh"https"http_url_syntax;Hashtbl.addh"pop"pop_url_syntax;Hashtbl.addh"pops"pop_url_syntax;Hashtbl.addh"imap"imap_url_syntax;Hashtbl.addh"imaps"imap_url_syntax;Hashtbl.addh"mailto"mailto_url_syntax;Hashtbl.addh"news"news_url_syntax;Hashtbl.addh"nntp"nntp_url_syntax;Hashtbl.addh"nntps"nntp_url_syntax;Hashtbl.addh"cid"cid_url_syntax;Hashtbl.addh"mid"cid_url_syntax;Hashtbl.addh"data"data_url_syntax;Hashtbl.addh"ipp"ipp_url_syntax;Hashtbl.addh"ipps"ipp_url_syntax;Hashtbl.addh"ldap"ldap_url_syntax;Hashtbl.addh"ldaps"ldap_url_syntax;h;;leturl_conforms_to_syntaxurl=letrecognized x=x<>Url_part_not_recognizedinletrequiredx=x=Url_part_requiredinletpresentx=x<>Noneinletsyn=url.url_syntaxin(presenturl.url_scheme=>recognizedsyn.url_enable_scheme)&&(present url.url_user =>recognizedsyn.url_enable_user)&&((url.url_user_param<>[])=>recognizedsyn.url_enable_user_param)&&(presenturl.url_password=>recognizedsyn.url_enable_password)&&(presenturl.url_host=>recognizedsyn.url_enable_host)&&(presenturl.url_port=>recognizedsyn.url_enable_port)&&((url.url_path<>[])=>recognizedsyn.url_enable_path)&&((url.url_param<>[])=>recognizedsyn.url_enable_param)&&(present url.url_query=>recognizedsyn.url_enable_query)&&(presenturl.url_fragment=>recognizedsyn.url_enable_fragment)&&(presenturl.url_other=>recognizedsyn.url_enable_other)&&(requiredsyn.url_enable_scheme=>presenturl.url_scheme)&&(requiredsyn.url_enable_user=>presenturl.url_user)&&(requiredsyn.url_enable_user_param=>(url.url_user_param<>[]))&&(requiredsyn.url_enable_password=>presenturl.url_password)&&(requiredsyn.url_enable_host=>presenturl.url_host)&&(requiredsyn.url_enable_port=>presenturl.url_port)&&(requiredsyn.url_enable_path=>(url.url_path<> []))&&(requiredsyn.url_enable_param=>(url.url_param<>[]))&&(requiredsyn.url_enable_query=>present url.url_query)&&(requiredsyn.url_enable_fragment=>presenturl.url_fragment)&&(requiredsyn.url_enable_other=>presenturl.url_other)&&(url.url_validity||syn.url_is_validurl);;leturl_syntax_of_urlurl=url.url_syntax;;lethost_of_addrip=matchNetsys.domain_of_inet_addripwith|Unix.PF_INET->Unix.string_of_inet_addrip|Unix.PF_INET6->"["^Unix.string_of_inet_addrip^"]"|_->assertfalseletmodify_url?syntax?(encoded=false)?scheme?user?user_param?password?host?addr?port?socksymbol?path?param?query?fragment?otherurl=letenc?(plus=false)x=ifencoded thenxelsematchxwithNone->None|Somex'->Some(Netencoding.Url.encode~plusx')inletenc_list ?(plus=false)l=ifencodedthenlelseList.map(Netencoding.Url.encode~plus)linletnew_syntax=matchsyntaxwithNone->url.url_syntax|Somesyn->syninletcheck_strings_optcats=matchs_opt withNone->()|Somes->letl=String.lengthsinletk=scan_url_parts0lcatsnew_syntax.url_accepts_8bitsin(* or raiseMalformed_URL *)ifk<>lthenraiseMalformed_URLinletcheck_string_listpcatssep=List.iter(funp_component->letl=String.lengthp_componentinletk=scan_url_partp_component0lcatsnew_syntax.url_accepts_8bitsin(* or raise Malformed_URL *)ifk<>lthenraiseMalformed_URL;ifString.containsp_componentsepthenraiseMalformed_URL;)pinletcheck_hosts=letl=String.lengthsinifString.lengths>= 2&&s.[0]='['then(ifs.[l-1]<>']'thenraiseMalformed_URL;letipv6=String.subs1(l-2)incheck_string(Someipv6)ipv6_cats)elsecheck_string(Somes)host_catsin(* Createthe modified record: *)leturl_host_0 =matchaddrwith|Somea->Some(host_of_addr a)|None->(matchhostwith|Someh->Someh|None->url.url_host)inleturl_port_0=matchportwith|Somep-> Somep|None->url.url_portinlet(url_host,url_port)=match(socksymbol:Netsockaddr.socksymboloption)with|Some(`Inet(ip,p))->(Some(host_of_addrip),Somep)|Some(`Inet_byname(h,p))->(Someh,Somep)|Some_->failwith"Neturl: Unacceptable socksymbol"|None->(url_host_0,url_port_0)inleturl'={url_syntax=new_syntax;url_validity=false;url_scheme=ifscheme =Nonethenurl.url_schemeelsescheme;url_user=ifuser=Nonethenurl.url_userelseencuser;url_user_param=(matchuser_paramwithNone->url.url_user_param|Somep->enc_listp);url_password =ifpassword=Nonethenurl.url_passwordelseencpassword;url_host=url_host;url_port =url_port;url_path=(matchpath withNone->url.url_path|Somep->enc_listp);url_param=(matchparamwithNone->url.url_param|Somep->enc_listp);url_query=ifquery =Nonethenurl.url_queryelse enc~plus:truequery;url_fragment=iffragment=Nonethenurl.url_fragmentelseencfragment;url_other=ifother=Nonethenurl.url_otherelseencother;}in(* Check whether the URL conforms to the syntax:
*)ifnot(url_conforms_to_syntaxurl')thenraiseMalformed_URL;ifurl'.url_password<>None&&url'.url_user=NonethenraiseMalformed_URL;ifurl'.url_user_param<>[]&&url'.url_user=None thenraiseMalformed_URL;ifurl'.url_user<>None&&url'.url_host=NonethenraiseMalformed_URL;ifurl'.url_port<>None&&url'.url_host=NonethenraiseMalformed_URL;(* Check every part: *)check_string url'.url_schemescheme_cats;check_stringurl'.url_userlogin_cats;check_string_listurl'.url_user_paramlogin_cats';';check_stringurl'.url_passwordlogin_cats;(matchurl'.url_hostwith|None->()|Somes->check_hosts);(matchurl'.url_portwithNone->()|Some p->ifp<0||p>65535thenraiseMalformed_URL);letpath_cats=path_cats_from_syntax new_syntax[]inletother_cats=other_cats_from_syntaxnew_syntax inletquery_cats=let syn={new_syntaxwithurl_enable_param=Url_part_not_recognized;url_enable_query=Url_part_not_recognized}inpath_cats_from_syntaxsyn[]incheck_stringurl'.url_queryquery_cats;check_stringurl'.url_fragmentpath_cats;check_stringurl'.url_otherother_cats;(* Check the lists: *)check_string_list url'.url_parampath_cats';';check_string_listurl'.url_pathpath_cats'/';(* Further path checks: *)beginmatchurl'.url_pathwith[]->(* The path is empty: There must not be 'param' *)ifurl'.url_host<>Nonethenbeginifurl'.url_param<>[]thenraiseMalformed_URL;(* if url'.url_query <> None then raise Malformed_URL;
* Allowed since RFC 2396
*)end|["";""]->(* This is illegal. *)raiseMalformed_URL;|""::p'->(* The path is absolute: ensure there is no double slash with host *)(matchurl'.url_pathwith|""::""::_->(* Double slash at beginning of path: Only allowed we have
* a host name!
*)ifhost=NonethenraiseMalformed_URL|_->())|_->(* The path is relative: there must not be a host *)ifurl'.url_host<>NonethenraiseMalformed_URL;end;(* Cache that the URL is valid: *)url'.url_validity<-true;url';;letnull_url={url_syntax=null_url_syntax;url_validity=true;url_scheme=None;url_user=None;url_user_param =[];url_password=None;url_host=None;url_port=None;url_path=[];url_param=[];url_query=None;url_fragment=None;url_other=None;};;letmake_url?(encoded=false)?scheme?user?user_param?password?host?addr?port?socksymbol?path?param?query?fragment?othersyntax=ifnot(url_syntax_is_validsyntax)theninvalid_arg"Neturl.make_url";modify_url~encoded~syntax?scheme?user?user_param?password?host?addr?port?socksymbol?path?param?query?fragment?othernull_url;;letremove_from_url?(scheme=false)?(user=false)?(user_param=false)?(password =false)?(host=false)?(port=false)?(path=false)?(param=false)?(query=false)?(fragment=false)?(other=false)url=make_url~encoded:true?scheme:(ifschemethenNoneelseurl.url_scheme)?user: (ifuserthenNoneelseurl.url_user)?user_param:(ifuser_paramthenNoneelseSomeurl.url_user_param)?password:(if passwordthenNoneelse url.url_password)?host:(ifhostthenNoneelseurl.url_host)?port:(ifportthenNoneelseurl.url_port)?path:(ifpaththenNoneelseSomeurl.url_path)?param:(ifparamthenNoneelseSomeurl.url_param)?query:(if querythenNoneelseurl.url_query)?fragment:(iffragmentthenNoneelseurl.url_fragment)?other:(ifotherthenNoneelseurl.url_other)url.url_syntax;;letdefault_url?(encoded =false)?scheme?user?(user_param=[])?password?host?port?(path=[])?(param=[])?query?fragment?otherurl=let encode=Netencoding.Url.encode~plus:trueinletencx=ifencodedthenxelsematchxwithNone->None|Somex'->Some(encode x')inletenc_listl=ifencoded thenlelseList.mapencodelinletpass_if_missingcurrentarg=matchcurrentwithNone->arg|_->currentinmake_url~encoded:true?scheme:(pass_if_missing url.url_schemescheme)?user:(pass_if_missingurl.url_user(encuser))~user_param:(ifurl.url_user_param=[]thenenc_listuser_paramelseurl.url_user_param)?password: (pass_if_missingurl.url_password(encpassword))?host:(pass_if_missing url.url_hosthost)?port:(pass_if_missingurl.url_port port)~path:(ifurl.url_path=[]thenenc_listpathelseurl.url_path)~param:(ifurl.url_param=[]thenenc_listparamelseurl.url_param)?query:(pass_if_missingurl.url_query(encquery))?fragment: (pass_if_missingurl.url_fragment (encfragment))?other:(pass_if_missingurl.url_other(encother))url.url_syntax;;letundefault_url?scheme?user?user_param?password?host?port?path?param?query?fragment?otherurl=letremove_if_matchingcurrentarg=matchcurrentwithNone->None|Somex->(matchargwithNone->current|Somex'->if x=x'thenNoneelsecurrent)inletremove_list_if_matchingcurrentarg=match argwithNone->current|Somex->ifx=currentthen[]elsecurrentinmake_url~encoded: true?scheme:(remove_if_matchingurl.url_schemescheme)?user:(remove_if_matchingurl.url_useruser)~user_param:(remove_list_if_matchingurl.url_user_paramuser_param)?password:(remove_if_matchingurl.url_passwordpassword)?host:(remove_if_matchingurl.url_hosthost)?port:(remove_if_matchingurl.url_port port)~path:(remove_list_if_matchingurl.url_pathpath)~param:(remove_list_if_matchingurl.url_paramparam)?query:(remove_if_matchingurl.url_queryquery)?fragment:(remove_if_matchingurl.url_fragmentfragment)?other:(remove_if_matchingurl.url_otherother)url.url_syntax;;leturl_provides ?(scheme=false)?(user=false)?(user_param=false)?(password=false)?(host=false)?(port=false)?(path=false)?(param=false)?(query=false)?(fragment=false)?(other=false)url=(scheme=>(url.url_scheme <>None))&&(user=>(url.url_user<>None))&&(user_param=>(url.url_param<>[]))&&(password =>(url.url_password<>None))&&(host =>(url.url_host<>None))&&(port =>(url.url_port<>None))&&(path=>(url.url_path<> []))&&(param =>(url.url_param<>[]))&&(query=>(url.url_query<>None))&&(fragment=>(url.url_fragment<>None))&&(other =>(url.url_other<>None));;letreturn_ifvalue=matchvaluewithNone->raiseNot_found|Somex->x;;letdecode_if?(plus=false)want_encodedvalue=letvalue'=return_if valueinifwant_encodedthenvalue'elseNetencoding.Url.decode~plusvalue';;let decode_path_if?(plus=false)want_encodedvalue=ifwant_encodedthenvalueelseList.map(Netencoding.Url.decode~plus)value;;leturl_schemeurl=return_if url.url_scheme;;leturl_user?(encoded=false)url=decode_ifencodedurl.url_user;;leturl_user_param?(encoded=false)url=decode_path_ifencodedurl.url_user_param;;leturl_password?(encoded=false)url=decode_ifencodedurl.url_password;;leturl_hosturl=return_ifurl.url_host;;leturl_port url=return_ifurl.url_port;;leturl_path?(encoded=false)url=decode_path_ifencodedurl.url_path;;leturl_param?(encoded=false)url=decode_path_ifencodedurl.url_param;;leturl_query?(encoded=false)url=decode_if~plus:trueencodedurl.url_query;;leturl_fragment?(encoded=false)url=decode_ifencodedurl.url_fragment;;leturl_other?(encoded=false)url=decode_ifencodedurl.url_other;;leturl_addrurl=matchurl.url_hostwith|None->raise Not_found|Someh->letl=String.lengthhinifl>=2&&h.[0]='['&&h.[l-1]=']'thenleta=String.subh1(l-2)intryUnix.inet_addr_of_stringawith_->raiseNot_foundelse(tryUnix.inet_addr_of_stringhwith_->raiseNot_found)leturl_socksymbolurldp=letp=matchurl.url_portwith|None->dp|Somep->pintryleta=url_addrurlin`Inet(a,p)with|Not_found->(matchurl.url_hostwith|None->raiseNot_found|Someh->`Inet_byname(h,p))letstring_of_urlurl=ifnot(url.url_validity)thenfailwith"Neturl.string_of_url: URLnot flagged as valid";(matchurl.url_schemewithNone -> ""|Somes->s^":")^(matchurl.url_hostwithNone->""|Somehost->"//"^(matchurl.url_userwithNone->""|Someuser->user^(String.concat""(List.map(funp->";"^p)url.url_user_param))^(matchurl.url_passwordwithNone->""|Somepassword->":"^password)^"@")^host^(matchurl.url_portwithNone->""|Someport->":" ^string_of_intport))^(matchurl.url_pathwith|[""]->"/"|x::pwhenurl.url_scheme=None &&url.url_host=None&&String.containsx':'->(* Really a special case: The colon contained in 'x' may cause
* that a prefix of 'x' is interpreted as URLscheme. In this
* case, "./" is prepended (as recommended in RFC 1808, 5.3).
*)"./"|_->"")^String.concat"/"url.url_path^(matchurl.url_otherwithNone->""|Someother->other)^String.concat""(List.map(fun s->";"^s)url.url_param)^(matchurl.url_querywithNone->""|Somequery->"?"^query)^(matchurl.url_fragmentwithNone->""|Somefragment->"#"^fragment);;letsemi_re=Netstring_str.regexp";";;leturl_of_stringurl_syntaxs=letl=String.lengthsinletrecognized x=x<>Url_part_not_recognizedinletreccollect_words?(ipv6=false)terminatorseof_charcatsk=(* Collect wordsas recognized by 'cats', starting at position 'k' in
* 's'. Collection stops if one the characters listed in 'terminators'
* is found. If the end of the string is reached, it is treated as
* 'eof_char'.
*
* if ipv6: words "[ipv6addr]" are also recognized.
*)letword,sep,k_end=ifipv6&&k<l&&s.[k]='['then(letk'=scan_url_parts(k+1)lipv6_sep_catsfalseinifk'>= lthen raise Malformed_URL;ifs.[k']<>']'thenraiseMalformed_URL;letword,sep=String.subsk(k'+1-k),(ifk'+1<lthens.[k'+1]elseeof_char)inif sep<>eof_charthen(ifcats.(Char.codesep)<>SeparatorthenraiseMalformed_URL);(word,sep,k'+1))else(letk'=scan_url_partsklcatsurl_syntax.url_accepts_8bitsin(* or raise Malformed_URL *)letword,sep=String.subsk(k'-k),(ifk'<lthens.[k']elseeof_char)in(word,sep,k'))inifList.memsepterminatorsthen[word,sep],k_endelseletword_sep_list',k_end'=collect_words~ipv6terminatorseof_charcats(k_end+1)in((word,sep)::word_sep_list'),k_end'in(* Try to extract the scheme name: *)letscheme,k1=ifrecognizedurl_syntax.url_enable_scheme thentryletk=scan_url_parts0lscheme_catsfalsein(* or raise Malformed_URL *)ifk=lthenraiseMalformed_URL;assert(s.[k]=':');Some(String.subs0k),(k+1)withMalformed_URL->None,0elseNone,0in(* If there is a "//", a host will follow: *)lethost,port,userinfo,password,k2=ifrecognizedurl_syntax.url_enable_host&&k1+2<=l&&s.[k1]='/'&&s.[k1+1]='/'thenbeginletword_sep_list,k'=collect_words ~ipv6:true['/';'?';'#']'/'login_cats(k1+2)in(* or raise Malformed_URL *)letintx=tryint_of_string xwith_->raiseMalformed_URLinmatchword_sep_listwith[host,('/'|'?'|'#')]->Somehost,None,None,None,k'|[host,':';port,('/'|'?'|'#')]->Somehost,Some(intport),None,None,k'|[user,'@';host,('/'|'?'|'#')]->Somehost,None,Someuser,None,k'|[user,'@';host,':';port,('/'|'?'|'#')]->Somehost,Some(intport),Someuser,None,k'|[user,':';password,'@';host,('/'|'?'|'#')]->Somehost,None,Someuser,Somepassword,k'|[user,':';password,'@';host,':';port,('/'|'?'|'#')]->Somehost,Some(intport),Someuser,Somepassword,k'|_->raiseMalformed_URLendelseNone,None,None,None,k1in(* Separate user from user_param: *)letuser,user_param =matchuserinfowithNone->(None,[])|Someu->ifrecognizedurl_syntax.url_enable_user_paramthen(letl=Netstring_str.split_delimsemi_reuinmatchlwith[]->(Some"",[])|user::user_param->(Someuser,user_param))else(Someu,[])inletpath,k3=ifrecognizedurl_syntax.url_enable_path&&k2<l(* && s.[k2]='/' *)thenbeginletcats=path_cats_from_syntaxurl_syntax['/']inletseps=separators_from_syntaxurl_syntaxin(* Note: '>' is not allowed within URLs; because of this we can use
* it as end-of-string character.
*)letword_sep_list,k'=collect_words('>'::seps)'>'catsk2in(* or raise Malformed_URL *)matchword_sep_listwith["",'/';"",_]->[""],k'|["",_]->[],k'|_->List.mapfstword_sep_list,k'endelsebegin(* If there is a single '/': skip it *)ifnot(recognizedurl_syntax.url_enable_other)&&k2<l&&s.[k2]='/'then[],(k2+1)else[],k2endinletother,k4=ifrecognizedurl_syntax.url_enable_other&&k3<lthen beginletcats=other_cats_from_syntaxurl_syntaxin(* Note: '>' is not allowed within URLs; because of this we can use
* it as end-of-string character.
*)letword_sep_list,k'=collect_words['>';'#']'>'catsk3in(* or raise Malformed_URL *)matchword_sep_listwith[other,_]->Someother,k'|_->assertfalseendelseNone,k3inletparam,k5=ifrecognized url_syntax.url_enable_param&&k4<l&&s.[k4]=';'thenbeginletcats=path_cats_from_syntax url_syntax []inletseps=separators_from_syntaxurl_syntaxinletseps'=List.filter(func->c<>';')sepsin(* Note: '>' is not allowed within URLs; because of this we can use
* it as end-of-string character.
*)letword_sep_list,k'=collect_words('>'::seps')'>'cats(k4+1)in(* or raise Malformed_URL *)List.mapfstword_sep_list,k'endelse[],k4inletquery,k6=ifrecognizedurl_syntax.url_enable_query&&k5<l&& s.[k5]='?'thenbeginletsyn={url_syntaxwithurl_enable_param =Url_part_not_recognized;url_enable_query=Url_part_not_recognized}inletcats=path_cats_from_syntaxsyn[]inletseps=separators_from_syntaxsynin(* Note: '>' is not allowed within URLs; because of this we can use
* it as end-of-string character.
*)letword_sep_list,k'=collect_words('>'::seps)'>'cats(k5+1)in(* or raise Malformed_URL *)matchword_sep_listwith[query,_]->Some query,k'|_->assertfalseendelseNone,k5inletfragment,k7=ifrecognizedurl_syntax.url_enable_fragment&&k6<l&&s.[k6]='#'thenbeginletcats=path_cats_from_syntaxurl_syntax[]inletseps=separators_from_syntaxurl_syntaxin(* Note: '>' is not allowed within URLs; because of this we can use * it as end-of-string character.
*)letword_sep_list,k'=collect_words('>'::seps)'>'cats(k6+1)in(* or raise Malformed_URL *)matchword_sep_listwith[fragment,_]->Somefragment,k'|_->assertfalseendelseNone,k6inifk7<>lthenraise Malformed_URL;make_url~encoded:true?scheme?user~user_param?password?host?port~path~param?query?fragment?otherurl_syntax;;letproblem_re=Netstring_str.regexp"[ <>\"{}|\\^`]"letproblem_hash_re=Netstring_str.regexp"[ <>\"{}|\\^`#]"letfixup_url_string?(escape_hash=false)=Netstring_str.global_substitute(ifescape_hashthenproblem_hash_reelseproblem_re)(funms->sprintf"%%%02x"(Char.codes.[Netstring_str.match_beginningm]));;letparse_url?(schemes=common_url_syntax)?base_syntax?(accept_8bits=false)?(enable_fragment=false)s=letscheme=trySome(extract_url_schemes)withMalformed_URL ->Noneinletsyntax=matchschemewithNone->(matchbase_syntaxwithNone->raiseMalformed_URL|Somesyn->partial_url_syntax syn)|Somesch->tryHashtbl.findschemesschwithNot_found->raiseMalformed_URLinletsyntax'=ifaccept_8bitsthen{syntaxwithurl_accepts_8bits =true}elsesyntax inletsyntax''=ifenable_fragment&&syntax.url_enable_fragment=Url_part_not_recognizedthen{syntax' withurl_enable_fragment=Url_part_allowed }elsesyntax'inurl_of_stringsyntax''s;;letsplit_paths=letl=String.lengthsinletreccollect_wordsk=letk'=tryString.index_fromsk'/'withNot_found->linletword=String.subsk(k'-k)inifk' >=lthen[word]elseword::collect_words(k'+1)inmatchcollect_words0with[""]->[]|["";""]->[""]|other->other;;letjoin_path l=matchlwith[""]->"/"|_->String.concat"/"l;;letnorm_pathl=letrecremove_slash_slashlfirst=matchlwith|[""]->[""]|["";""]whenfirst->[""]|""::l'whennotfirst->remove_slash_slashl'false|x::l'->x::remove_slash_slashl'false|[]->[]inlet recremove_dotlfirst=matchlwith|(["."]|[".";""])->iffirstthen[]else[""]|"."::x::l'->remove_dot (x::l')false|x::l'->x::remove_dotl'false|[]->[]inletrecremove_dot_dot_oncelfirst=matchlwithx::".."::[]whenx<>""&&x<>".."&¬first->[""]|x::".."::l'whenx<>""&&x<>".."->l'|x::l'->x::remove_dot_dot_oncel'false|[]->raiseNot_foundinletrecremove_dot_dotl=tryletl'=remove_dot_dot_onceltrueinremove_dot_dotl'withNot_found->linletl'=remove_dot_dot (remove_dot(remove_slash_slashltrue)true)inmatchl'with[".."]->["..";""]|["";""]-> [""]|_->l';;letapply_relative_url baseurlrelurl=ifnot(baseurl.url_validity)||not(relurl.url_validity)thenfailwith"Neturl.apply_relative_url: URL not flagged as valid";ifrelurl.url_scheme<>Nonethenmodify_url~syntax:baseurl.url_syntax(* inherit syntax *)relurlelseifrelurl.url_host<>Nonethenmodify_url~syntax:baseurl.url_syntax(* inherit syntax and scheme *)?scheme:baseurl.url_schemerelurlelsematchrelurl.url_pathwith""::other->(* An absolute path *)modify_url~syntax:baseurl.url_syntax(* inherit syntax, scheme, and *)~encoded:true?scheme:baseurl.url_scheme(* login info *)?host:baseurl.url_host?port:baseurl.url_port?user:baseurl.url_user~user_param:baseurl.url_user_param?password:baseurl.url_passwordrelurl|[]->(* Empty: Inherit also path, params, query, and fragment *)letnew_params,new_query,new_fragment=matchrelurl.url_param,relurl.url_query,relurl.url_fragmentwith[],None,None->(* Inherit all three *)baseurl.url_param,baseurl.url_query,baseurl.url_fragment|[],None,f->(* Inherit params and query *)baseurl.url_param,baseurl.url_query,f|[],q,f->(* Inherit params *)baseurl.url_param,q,f|p,q,f->(* Inherit none of them *)p,q,finmodify_url~syntax:baseurl.url_syntax~encoded:true?scheme:baseurl.url_scheme?host:baseurl.url_host?port:baseurl.url_port?user:baseurl.url_user~user_param:baseurl.url_user_param?password:baseurl.url_password~path:baseurl.url_path~param:new_params?query:new_query?fragment:new_fragmentrelurl|relpath->(* A relative path *)letrecchange_path basepath=matchbasepathwith|[]->ifbaseurl.url_host =Nonethenrelpathelse""::relpath|[""]->""::relpath|[x]->relpath|x::basepath'->x::change_pathbasepath'inletnew_path=norm_path(change_pathbaseurl.url_path)inmodify_url~syntax:baseurl.url_syntax(* inherit syntax, scheme, and *)~encoded:true?scheme:baseurl.url_scheme(* login info *)?host:baseurl.url_host?port:baseurl.url_port?user:baseurl.url_user~user_param:baseurl.url_user_param?password:baseurl.url_password~path:new_path(* and change path *)relurl;;letensure_absolute_url?baseu=ifu.url_scheme=Nonethen(matchbasewithNone->raiseMalformed_URL|Someb->apply_relative_url bu)elseu;;letprint_urlurl=Format.print_string("<URL:"^string_of_urlurl^">");;letbackslash_re=Netstring_str.regexp"\\\\";;letdrive_letter_re =Netstring_str.regexp"^\\([A-Za-z]\\):/";;letdrive_letter_re'=Netstring_str.regexp"^/\\([A-Za-z]\\):/";;letunc_path_re=Netstring_str.regexp "^//\\([^/]+\\)\\(/|$\\)";;letos_type=Sys.os_type;;letclassify_pathp=matchos_typewith"Unix"->ifp<>""&&p.[0]='/'then`Absolute_localpelse`Relativep|"Win32"->letp'=Netstring_str.global_replacebackslash_re"/"pin(matchNetstring_str.string_matchdrive_letter_rep'0withSomem->`Absolute_local("/"^p')|None->(matchNetstring_str.string_matchunc_path_rep'0withSomem->lethost=Netstring_str.matched_groupm1p'inlethost_e=Netstring_str.group_end m1inletpath=String.subp'host_e(String.lengthp'-host_e)inletpath =ifpath=""then"/"elsepathin`Absolute_remote(host,path)|None->ifp'<>""&&p'.[0]='/'then`Relative_drivep'else`Relativep'))|"Cygwin"->letp'=Netstring_str.global_replace backslash_re"/"pin(matchNetstring_str.string_matchdrive_letter_rep'0withSomem->letletter=Netstring_str.matched_groupm1p'inletrest=String.subp'2(String.lengthp'-2)in`Absolute_local("/cygdrive/"^letter^rest)|None->(matchNetstring_str.string_matchunc_path_rep'0withSomem->lethost=Netstring_str.matched_group m1p'inlethost_e=Netstring_str.group_end m1inletpath=String.subp'host_e(String.lengthp'-host_e)inletpath =ifpath=""then"/"elsepathin`Absolute_remote(host,path)|None->ifp'<>""&&p'.[0]='/'then`Absolute_localp'else`Relativep'))|_->assert false;;letfile_url_of_local_path?(getcwd=Sys.getcwd)p=(* Classify p, and make it absolute: *)letp_class=classify_pathpinletp_abs_class=matchp_classwith`Relativer->(matchclassify_path(getcwd())with`Absolute_locall->ifl="/"then`Absolute_local("/"^r)else`Absolute_local(l^"/"^r)|`Absolute_remote(h,l)->ifl="/"then`Absolute_remote(h,"/"^r)else`Absolute_remote(h,l^"/"^r)|_->failwith"Neturl.file_url_of_local_path: cwd is not absolute")|`Relative_driver->(matchclassify_path(getcwd())with`Absolute_locall->(matchNetstring_str.string_matchdrive_letter_re'l0withSomem->letletter=Netstring_str.matched_groupm1lin`Absolute_local("/"^letter ^":"^r)|None->assertfalse)|`Absolute_remote(h,l)->`Absolute_remote(h,r)|_->failwith"Neturl.file_url_of_local_path: cwd is not absolute")|other->otherin(* Generate the URL: *)letsyntax={file_url_syntaxwithurl_accepts_8bits=true}inmatchp_abs_classwith`Absolute_locall->letpath=split_path linmake_url~scheme:"file"~host:"localhost"~pathsyntax|`Absolute_remote(host,l)->letpath=split_pathlinmake_url~scheme:"file"~host~pathsyntax|_->assertfalse;;letdrive_letter_comp_re=Netstring_str.regexp"^\\([A-Za-z]\\)\\(:|\\|\\)";;letlocal_path_of_file_url u=letlocal_pathp=ifp=[]||List.hdp<>""thenfailwith"Neturl.local_path_of_file_url: URL is not absolute";matchos_type with"Unix"->join_path p|"Win32"->(* There must be a drive letter: *)(matchpwith(""::drive::rest)->(matchNetstring_str.string_matchdrive_letter_comp_redrive0withSomem->letletter=Netstring_str.matched_groupm1driveinletrest=ifrest=[]then[""]elserestinjoin_path((letter^":"):: rest)|None->failwith"Neturl.local_path_of_file_url: URL is not absolute";)|_->failwith"Neturl.local_path_of_file_url: URL is not absolute";)|"Cygwin"->(* Recognize drive letters: *)(matchpwith(""::drive::rest)->(matchNetstring_str.string_matchdrive_letter_comp_redrive0withSomem->letletter=Netstring_str.matched_groupm1driveinjoin_path(""::"cygdrive"::letter::rest)|None->join_pathp)|_->join_pathp)|_->assertfalseinletremote_path hostp=ifp=[]||List.hdp<>""thenfailwith"Neturl.local_path_of_file_url: URL is not absolute";matchos_type with"Unix"->failwith"Neturl.local_path_of_file_url: Cannot process non-local file URLs"|"Win32"|"Cygwin"->join_path(""::""::host::List.tlp)|_->assertfalseinletoptf=trySome(fu)withNot_found->Noneinmatch(opturl_scheme),(opturl_host),(url_pathu)with(Some"file",(Some("localhost"|"")|None),[])->local_path[""]|(Some"file",(Some("localhost"|"")|None),p)->local_pathp|(Some"file",Somehost,[])->remote_pathhost[""]|(Some"file",Somehost,p)->remote_pathhostp|(Some_,_,_)->failwith"Neturl.local_path_of_file_url: Unexpected scheme"|(None,_,_)->failwith"Neturl.local_path_of_file_url: Missing scheme (relative URL?)";;