123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983(* Ocsigen
* http://www.ocsigen.org
* Module ocsigen_extensions.ml
* Copyright (C) 2005 2007 Vincent Balat
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, with linking exception;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*)letsection=Lwt_log.Section.make"ocsigen:ext"openLwt.InfixmoduleUrl=Ocsigen_lib.UrlincludeOcsigen_commandexceptionOcsigen_http_error=Ocsigen_cohttp.Ocsigen_http_errorexceptionOcsigen_Looping_request(** Xml tag not recognized by an extension (usually not a real error) *)exceptionBad_config_tag_for_extensionofstring(** Error in a <site> tag inside the main ocsigen.conf file *)exceptionError_in_config_fileofstring(** Option incorrect in a userconf file *)exceptionError_in_user_config_fileofstringtypefile_info=Ocsigen_multipart.file_info={tmp_filename:string;filesize:int64;raw_original_filename:string;file_content_type:((string*string)*(string*string)list)option}letbadconfigfmt=Printf.ksprintf(funs->raise(Error_in_config_files))fmt(* virtual hosts: *)typevirtual_hosts=(string*Pcre.regexp*intoption)list(* We cannot use generic comparison, as regexpes are abstract values that
cannot be compared or hashed. However the string essentially contains
the value that is compiled into a regexp, and we compare this instead *)lethash_virtual_hosts(l:virtual_hosts)=Hashtbl.hash(List.map(fun(s,_,p)->(s,p))l)letrecequal_virtual_hosts(l1:virtual_hosts)(l2:virtual_hosts)=matchl1,l2with|[],[]->true|[],_::_|_::_,[]->false|(s1,_,p1)::q1,(s2,_,p2)::q2->s1=s2&&p1=p2&&equal_virtual_hostsq1q2(* Server configuration, for local files that must not be sent *)typedo_not_serve={do_not_serve_regexps:stringlist;do_not_serve_files:stringlist;do_not_serve_extensions:stringlist;}letserve_everything={do_not_serve_regexps=[];do_not_serve_files=[];do_not_serve_extensions=[]}(* BY TODO : Use unbalanced trees instead *)letjoin_do_not_served1d2={do_not_serve_regexps=d1.do_not_serve_regexps@d2.do_not_serve_regexps;do_not_serve_files=d1.do_not_serve_files@d2.do_not_serve_files;do_not_serve_extensions=d1.do_not_serve_extensions@d2.do_not_serve_extensions;}lethash_consed_do_not_serve=Hashtbl.create17exceptionIncorrectRegexpesofdo_not_serveletdo_not_serve_to_regexpd=tryHashtbl.findhash_consed_do_not_servedwithNot_found->letwrapl=ifl=[]thenNoneelseSomelandbindf=functionNone->None|Somev->Some(fv)inletfiles,extensions,regexps=wrapd.do_not_serve_files,wrapd.do_not_serve_extensions,wrapd.do_not_serve_regexpsinletparen_quotel=String.concat"|"(List.map(funs->Printf.sprintf"(%s)"(Pcre.quotes))l)andparenl=String.concat"|"(List.map(funs->Printf.sprintf"(%s)"s)l)inletfiles=bindparen_quotefilesandextensions=bindparen_quoteextensionsandregexps=bindparenregexpsinletfiles=bind(Printf.sprintf".*/(%s)")filesandextensions=bind(Printf.sprintf".*\\.(%s)")extensionsinletl=List.fold_left(funr->functionNone->r|Somev->v::r)[][files;extensions;regexps]inletregexp=ifl=[]then(* This regexp should not never match *)"$^"elsePrintf.sprintf"^(%s)$"(parenl)in(tryLwt_log.ign_info_f~section"Compiling exclusion regexp %s"regexp;letr=Ocsigen_lib.Netstring_pcre.regexpregexpinHashtbl.addhash_consed_do_not_servedr;rwith_->raise(IncorrectRegexpesd))typeconfig_info={default_hostname:string;default_httpport:int;default_httpsport:int;default_protocol_is_https:bool;mime_assoc:Ocsigen_charset_mime.mime_assoc;charset_assoc:Ocsigen_charset_mime.charset_assoc;default_directory_index:stringlist;(** Default name to use as index file
when a directory is requested.
Use [None] if no index should be
tried. The various indexes are
tried in the given order. If no
index is specified, or the index
does not exists, the content of
the directory might be listed,
according to
[list_directry_content] *)list_directory_content:bool;(** Should the list of files in a directory be
displayed if there is no index in this
directory ? *)follow_symlinks:[`No|`Owner_match|`Always];(** Should symlinks be
followed when accessign a
local file? *)do_not_serve_404:do_not_serve;do_not_serve_403:do_not_serve;uploaddir:stringoption;maxuploadfilesize:int64option;}letdefault_config_info()=letdo_not_serve_404={do_not_serve_regexps=[];do_not_serve_files=[];do_not_serve_extensions=[];}in{default_hostname=Unix.gethostname();default_httpport=Ocsigen_config.get_default_port();default_httpsport=Ocsigen_config.get_default_sslport();default_protocol_is_https=false;mime_assoc=Ocsigen_charset_mime.default_mime_assoc();charset_assoc=Ocsigen_charset_mime.empty_charset_assoc?default:(Ocsigen_config.get_default_charset())();default_directory_index=["index.html"];list_directory_content=false;follow_symlinks=`Owner_match;do_not_serve_404;do_not_serve_403=do_not_serve_404;uploaddir=Ocsigen_config.get_uploaddir();maxuploadfilesize=Ocsigen_config.get_maxuploadfilesize();}(* Requests *)typerequest={request_info:Ocsigen_request.t;request_config:config_info;}exceptionOcsigen_is_dir=Ocsigen_cohttp.Ocsigen_is_dirtypeanswer=|Ext_do_nothing(** I don't want to do anything *)|Ext_foundof(unit->Ocsigen_response.tLwt.t)(** "OK stop! I will take the page. You can start the following
request of the same pipelined connection. Here is the function
to generate the page". The extension must return Ext_found as
soon as possible when it is sure it is safe to start next
request. Usually immediately. But in some case, for example
proxies, you don't want the request of one connection to be
handled in different order. (for example revproxy.ml starts its
requests to another server before returning Ext_found, to ensure
that all requests are done in same order). *)|Ext_found_stopof(unit->Ocsigen_response.tLwt.t)(** Found but do not try next extensions *)|Ext_nextofCohttp.Code.status(** Page not found. Try next extension. The status is usually
`Not_found, but may be for example `Forbidden (403) if you want
to try another extension afterwards. Same as Ext_continue_with
but does not change the request. *)|Ext_stop_siteof(Ocsigen_cookie_map.t*Cohttp.Code.status)(** Error. Do not try next extension, but try next site. *)|Ext_stop_hostof(Ocsigen_cookie_map.t*Cohttp.Code.status)(** Error.
Do not try next extension,
do not try next site,
but try next host. *)|Ext_stop_allof(Ocsigen_cookie_map.t*Cohttp.Code.status)(** Error. Do not try next extension,
do not try next site,
do not try next host. *)|Ext_continue_withof(request*Ocsigen_cookie_map.t*Cohttp.Code.status)(** Used to modify the request before giving it to next extension.
The extension returns the request (possibly modified) and a set
of cookies if it wants to set or cookies
({!Ocsigen_cookie_set.empty} for no cookies). You must add
these cookies yourself in request if you want them to be seen by
subsequent extensions, for example using
{!Ocsigen_http_frame.compute_new_ri_cookies}. The status is
usually equal to the one received from preceding extension (but
you may want to modify it). *)|Ext_retry_withofrequest*Ocsigen_cookie_map.t(** Used to retry all the extensions with a new request. The
extension returns the request (possibly modified) and a set of
cookies if it wants to set or cookies
({!Ocsigen_cookie_set.empty} for no cookies). You must add
these cookies yourself in request if you want them to be seen by
subsequent extensions, for example using
{!Ocsigen_http_frame.compute_new_ri_cookies}. *)|Ext_sub_resultofextension_composite(** Used if your extension want to define option that may contain
other options from other extensions. In that case, while
parsing the configuration file, call the parsing function (of
type [parse_fun]), that will return something of type
[extension_composite]. *)|Ext_found_continue_withof(unit->(Ocsigen_response.t*request)Lwt.t)(** Same as [Ext_found] but may modify the request. *)|Ext_found_continue_with'of(Ocsigen_response.t*request)(** Same as [Ext_found_continue_with] but does not allow to delay
the computation of the page. You should probably not use it, but
for output filters. *)andrequest_state=|Req_not_foundof(Cohttp.Code.status*request)|Req_foundof(request*Ocsigen_response.t)andextension_composite=Ocsigen_cookie_map.t->request_state->(answer*Ocsigen_cookie_map.t)Lwt.ttypeextension=request_state->answerLwt.ttypeparse_fun=Xml.xmllist->extension_compositetypeparse_host=Parse_hostof(Url.path->parse_host->parse_fun->Xml.xml->extension)lethosts:(virtual_hosts*config_info*extension_composite)listref=ref[]letset_hostsv=hosts:=vletget_hosts()=!hosts(* Default hostname is either the Host header or the hostname set in
the configuration file. *)letget_hostname{request_info;request_config={default_hostname;_};_}=ifOcsigen_config.get_usedefaulthostname()thendefault_hostnameelsematchOcsigen_request.hostrequest_infowith|None->default_hostname|Somehost->host(* Default port is either
- the port the server is listening at
- or the port in the Host header
- or the default port set in the configuration file. *)letget_port{request_info;request_config={default_httpport;default_httpsport;_}}=ifOcsigen_config.get_usedefaulthostname()thenifOcsigen_request.sslrequest_infothendefault_httpsportelsedefault_httpportelseOcsigen_request.portrequest_infoletnew_url_of_directory_requestrequestri=Lwt_log.ign_info~section"Sending 301 Moved permanently";letssl=Ocsigen_request.sslriinletscheme=ifsslthen"https"else"http"andhost=get_hostnamerequestandport=letport=get_portrequestinifport=ifsslthen443else80thenNoneelseSomeportandpath=letpath=Ocsigen_request.path_stringriinifpath.[String.lengthpath-1]='/'thenpathelsepath^"/"andquery=Ocsigen_request.get_paramsriinUri.make~scheme~host?port~path~query()(* To give parameters to extensions: *)letdynlinkconfig=ref([]:Xml.xmllist)letset_configs=dynlinkconfig:=sletget_config()=!dynlinkconfigletsite_matchrequest(site_path:stringlist)url=(* We are sure that there is no / at the end or beginning of site_path *)(* and no / at the beginning of url *)(* and no // or ../ inside both of them *)(* We return the subpath without / at beginning *)letrecauxsite_pathurl=matchsite_path,urlwith|[],[]->raise(Ocsigen_is_dir(new_url_of_directory_requestrequest))|[],p->Somep|a::l,aa::llwhena=aa->auxlll|_->Noneinmatchsite_path,urlwith|[],[]->Some[]|_->auxsite_pathurlletdefault_extension_composite:extension_composite=funcookies_to_set->function|Req_found(ri,res)->Lwt.return(Ext_found_continue_with'(res,ri),cookies_to_set)|Req_not_found(e,ri)->Lwt.return(Ext_continue_with(ri,Ocsigen_cookie_map.empty,e),cookies_to_set)letcompose_step(f:extension)(g:extension_composite):extension_composite=funcookies_to_setreq_state->freq_state>>=funres->letrecauxcookies_to_set=function|Ext_do_nothing->gcookies_to_setreq_state|Ext_foundr->r()>>=funr'->letri=matchreq_statewith|Req_found(ri,_)->ri|Req_not_found(_,ri)->riingOcsigen_cookie_map.empty(Req_found(ri,Ocsigen_response.add_cookiesr'cookies_to_set))|Ext_found_continue_withr->r()>>=fun(r',req)->gOcsigen_cookie_map.empty(Req_found(req,Ocsigen_response.add_cookiesr'cookies_to_set))|Ext_found_continue_with'(r',req)->gOcsigen_cookie_map.empty(Req_found(req,Ocsigen_response.add_cookiesr'cookies_to_set))|Ext_nexte->letri=matchreq_statewith|Req_found(ri,_)->ri|Req_not_found(_,ri)->riingcookies_to_set(Req_not_found(e,ri))|Ext_continue_with(ri,cook,e)->g(Ocsigen_cookie_map.add_multicookcookies_to_set)(Req_not_found(e,ri))|Ext_found_stop_|Ext_stop_site_|Ext_stop_host_|Ext_stop_all_|Ext_retry_with_asres->Lwt.return(res,cookies_to_set)|Ext_sub_resultsr->srcookies_to_setreq_state>>=fun(res,cookies_to_set)->auxcookies_to_setresinauxcookies_to_setresletreccompose=function|[]->default_extension_composite|e::rest->compose_stepe(composerest)letfun_end=ref(fun()->())letfun_exn=ref(funexn->(raiseexn:string))letrecparse_site_attrs(enc,dir)=function|[]->(matchdirwith|None->raise(Ocsigen_config.Config_file_error("Missing dir attribute in <site>"))|Somes->enc,s)|("path",s)::rest|("dir",s)::rest->(matchdirwith|None->parse_site_attrs(enc,Somes)rest|_->raise(Ocsigen_config.Config_file_error("Duplicate attribute dir in <site>")))|("charset",s)::rest->(matchencwith|None->parse_site_attrs((Somes),dir)rest|_->raise(Ocsigen_config.Config_file_error("Duplicate attribute charset in <site>")))|(s,_)::_->raise(Ocsigen_config.Config_file_error("Wrong attribute for <site>: "^s))letmake_parse_configpathparse_hostl:extension_composite=letf=parse_hostpath(Parse_hostparse_host)in(* creates all site data, if any *)letrecparse_config:_->extension_composite=function|[]->default_extension_composite|xmltag::ll->try(* The evaluation order is important here *)letf=fparse_configxmltagincompose_stepf(parse_configll)with|Bad_config_tag_for_extensiont->(* This case happens only if no extension has recognized the
tag at all *)badconfig"Unexpected tag <%s> inside <site dir=\"%s\">"t(Url.string_of_url_path~encode:truepath)|Error_in_config_file_ase->raisee|e->badconfig"Error while parsing configuration file: %s"(try!fun_exnewithe->Printexc.to_stringe)inletr=tryparse_configlwithe->!fun_end();raisee(*VVV Maybe we should avoid calling fun_end after parsing user
config files (with extension userconf) ... See eliommod.ml *)in!fun_end();rletsite_extext_of_childrencharsetpathcookies_to_set=function|Req_found(ri,res)->Lwt.return(Ext_found_continue_with'(res,ri),cookies_to_set)|Req_not_found(e,oldri)->letoldri=matchcharsetwith|None->oldri|Somecharset->{oldriwithrequest_config={oldri.request_configwithcharset_assoc=Ocsigen_charset_mime.set_default_charsetoldri.request_config.charset_assoccharset}}inmatchsite_matcholdripath(Ocsigen_request.patholdri.request_info)with|None->Lwt_log.ign_info_f~section"site \"%a\" does not match url \"%a\"."(fun()path->Url.string_of_url_path~encode:truepath)path(fun()oldri->Url.string_of_url_path~encode:true(Ocsigen_request.patholdri.request_info))oldri;Lwt.return(Ext_nexte,cookies_to_set)|Somesub_path->Lwt_log.ign_info_f~section"site found: url \"%a\" matches \"%a\"."(fun()oldri->Url.string_of_url_path~encode:true(Ocsigen_request.patholdri.request_info))oldri(fun()path->Url.string_of_url_path~encode:truepath)path;letri={oldriwithrequest_info=Ocsigen_request.updateoldri.request_info~sub_path:(Url.string_of_url_path~encode:truesub_path)}inext_of_childrencookies_to_set(Req_not_found(e,ri))>>=function(* After a site, we turn back to old ri *)|(Ext_stop_site(cs,err),cookies_to_set)|(Ext_continue_with(_,cs,err),cookies_to_set)->Lwt.return(Ext_continue_with(oldri,cs,err),cookies_to_set)|(Ext_found_continue_withr,cookies_to_set)->r()>>=fun(r',_req)->Lwt.return(Ext_found_continue_with'(r',oldri),cookies_to_set)|(Ext_found_continue_with'(r,_req),cookies_to_set)->Lwt.return(Ext_found_continue_with'(r,oldri),cookies_to_set)|(Ext_do_nothing,cookies_to_set)->Lwt.return(Ext_continue_with(oldri,Ocsigen_cookie_map.empty,e),cookies_to_set)|r->Lwt.returnrletsite_extext_of_childrencharsetpath:extension=function|Req_found(ri,r)->Lwt.return(Ext_found_continue_with'(r,ri))|Req_not_found_->Lwt.return(Ext_sub_result(site_extext_of_childrencharsetpath))letpreprocess_site_pathp=Url.(remove_dotdotp|>remove_slash_at_beginning|>remove_slash_at_end)(* Implements only <site> parsing. Uses parse_host to recursively
parse children of <site>. *)letdefault_parse_config_userconf_info(_host:virtual_hosts)_config_infoprevpath(Parse_hostparse_host)(_parse_fun:parse_fun)=function|Xml.Element("site",atts,l)->letcharset,dir=parse_site_attrs(None,None)attsinletpath=prevpath@preprocess_site_path(Url.split_pathdir)inletext_of_children=make_parse_configpathparse_hostlinsite_extext_of_childrencharsetpath|Xml.Element(tag,_,_)->raise(Bad_config_tag_for_extensiontag)|_->raise(Ocsigen_config.Config_file_error("Unexpected content inside <host>"))typeuserconf_info={localfiles_root:string;}typeparse_config=userconf_infooption->virtual_hosts->config_info->parse_config_auxandparse_config_aux=Url.path->parse_host->(parse_fun->Xml.xml->extension)let_extension_void_fun_site:parse_config=fun______->function|Xml.Element(t,_,_)->raise(Bad_config_tag_for_extensiont)|_->raise(Error_in_config_file"Unexpected data in config file")letregister,parse_config_item,get_init_exn_handler=letref_fun_site=refdefault_parse_configin(fun?fun_site?end_init?(exn_handler=raise)?(respect_pipeline=false)()->ifrespect_pipelinethenOcsigen_config.set_respect_pipeline();(matchfun_sitewith|None->()|Somefun_site->letold_fun_site=!ref_fun_siteinref_fun_site:=(funpathhostconf_info->letoldf=old_fun_sitepathhostconf_infoinletnewf=fun_sitepathhostconf_infoinfunpathparse_host->letoldf=oldfpathparse_hostinletnewf=newfpathparse_hostinfunparse_configconfig_tag->tryoldfparse_configconfig_tagwith|Bad_config_tag_for_extension_c->newfparse_configconfig_tag));(matchend_initwith|Someend_init->fun_end:=Ocsigen_lib.compend_init!fun_end;|None->());letcurexnfun=!fun_exninfun_exn:=fune->trycurexnfunewithe->exn_handlere),(funpathhostconf->!ref_fun_sitepathhostconf),(fun()->!fun_exn)letdefault_parse_extensionext_name=function|[]->()|_->raise(Error_in_config_file(Printf.sprintf"Unexpected content found in configuration of extension %s: %s does not accept options"ext_nameext_name))letregister~name?fun_site?end_init?init_fun?exn_handler?respect_pipeline()=Ocsigen_loader.set_module_init_functionname(fun()->(matchinit_funwith|None->default_parse_extensionname(get_config())|Somef->f(get_config()));register?fun_site?end_init?exn_handler?respect_pipeline())moduleConfiguration=structtypeattribute'={attribute_obligatory:bool;attribute_value_func:string->unit}typeattribute=string*attribute'typeelement'={obligatory:bool;init:unit->unit;elements:elementlist;attributes:attributelist;pcdata:(string->unit)option;other_elements:(string->(string*string)list->Xml.xmllist->unit)option;other_attributes:(string->string->unit)option;}andelement=string*element'letelement~name?(obligatory=false)?(init=ignore)?(elements=[])?(attributes=[])?pcdata?other_elements?other_attributes():element=name,{obligatory;init;elements;attributes;pcdata;other_elements;other_attributes}letattribute~name?(obligatory=false)f:attribute=name,{attribute_obligatory=obligatory;attribute_value_func=f}letignore_blank_pcdata~in_tag=funstr->String.iter(func->ifnot(List.memc[' ';'\n';'\r';'\t'])thenraise(Error_in_user_config_file("Non-blank PCDATA in tag "^in_tag)))strletrefuse_pcdata~in_tag=fun_->raise(Error_in_user_config_file("No PCDATA allowed in tag "^in_tag))letcheck_attribute_occurrence~in_tagattributes=function|name,{attribute_obligatory=true;_}->(tryignore(List.assocnameattributes)withNot_found->raise(Error_in_user_config_file("Obligatory attribute "^name^" not in tag "^in_tag)))|_->()letcheck_element_occurrence~in_tagelements=function|name,{obligatory=true;_}->letcorresponding_element=function|Xml.Element(name',_,_)->name=name'|_->falseinifnot(List.existscorresponding_elementelements)thenraise(Error_in_user_config_file("Obligatory element "^name^" not in tag "^in_tag))|_->()letprocess_attribute=fun~in_tag~attributes:spec_attributes?other_attributes:spec_other_attributes(attribute,value)->try(List.assocattributespec_attributes).attribute_value_funcvaluewithNot_found->matchspec_other_attributeswith|Somespec_other_attributes->spec_other_attributesattributevalue|None->raise(Error_in_user_config_file("Unexpected attribute "^attribute^" in tag "^in_tag))letrecprocess_element~in_tag~elements:spec_elements?pcdata:spec_pcdata?other_elements:spec_other_elements=function|Xml.PCDatastr->letspec_pcdata=Ocsigen_lib.Option.get(fun()->ignore_blank_pcdata~in_tag)spec_pcdatainspec_pcdatastr|Xml.Element(name,attributes,elements)->tryletspec=List.assocnamespec_elementsinList.iter(check_attribute_occurrence~in_tag:nameattributes)spec.attributes;List.iter(check_element_occurrence~in_tag:nameelements)spec.elements;spec.init();List.iter(process_attribute~in_tag:name~attributes:spec.attributes?other_attributes:spec.other_attributes)attributes;List.iter(process_element~in_tag:name~elements:spec.elements?pcdata:spec.pcdata?other_elements:spec.other_elements)elementswithNot_found->matchspec_other_elementswith|Somespec_other_elements->spec_other_elementsnameattributeselements|None->raise(Error_in_config_file("Unknown tag "^name^" in tag "^in_tag))letprocess_elements~in_tag~elements:spec_elements?pcdata?other_elements?(init=ignore)elements=List.iter(check_element_occurrence~in_tagelements)spec_elements;init();List.iter(process_element~in_tag~elements:spec_elements?pcdata?other_elements)elementsendletstart_initialisation,during_initialisation,end_initialisation,get_numberofreloads=letinit=reftrueinletnb=ref(-1)in((fun()->init:=true;nb:=!nb+1;),(fun()->!init),(fun()->init:=false;),(fun()->!nb))lethost_match~(virtual_hosts:virtual_hosts)~host~port=letport_match=function|None->true|Somep->p=portinmatchhostwith|None->List.exists(fun(_,_,p)->port_matchp)virtual_hosts(*VVV Warning! For HTTP/1.0, when host is absent,
we take the first one, even if it doesn't match! *)|Somehost->lethost_matchregexp=(Ocsigen_lib.Netstring_pcre.string_matchregexphost0<>None)inletrecaux=function|[]->false|(_,a,p)::l->(port_matchp&&host_matcha)||auxlinauxvirtual_hosts(* Currently used only for error messages. *)letstring_of_host(h:virtual_hosts)=letaux1(host,_,port)=matchportwith|None->host|Somep->host^":"^string_of_intpinList.fold_left(fundarg->d^aux1arg^" ")""hletcompute_result?(previous_cookies=Ocsigen_cookie_map.empty)request_info=lethost=Ocsigen_request.hostrequest_infoandport=Ocsigen_request.portrequest_infoinletstring_of_host_option=function|None->"<no host>:"^(string_of_intport)|Someh->h^":"^(string_of_intport)inletrecfold_hostsrequest_info(prev_err:Cohttp.Code.status)cookies_to_set=function|[]->Lwt.fail(Ocsigen_http_error(cookies_to_set,prev_err))|(virtual_hosts,request_config,host_function)::lwhenhost_match~virtual_hosts~host~port->Lwt_log.ign_info_f~section"host found! %a matches %a"(fun()->string_of_host_option)host(fun()->string_of_host)virtual_hosts;host_functioncookies_to_set(Req_not_found(prev_err,{request_info;request_config}))>>=fun(res_ext,cookies_to_set)->(matchres_extwith|Ext_foundr|Ext_found_stopr->r()>>=funr'->Lwt.return(Ocsigen_response.add_cookiesr'cookies_to_set)|Ext_do_nothing->fold_hostsrequest_infoprev_errcookies_to_setl|Ext_found_continue_withr->r()>>=fun(r',_)->Lwt.return(Ocsigen_response.add_cookiesr'cookies_to_set)|Ext_found_continue_with'(r,_)->Lwt.return(Ocsigen_response.add_cookiesrcookies_to_set)|Ext_nexte->fold_hostsrequest_infoecookies_to_setl(* try next site *)|Ext_stop_host(cook,e)|Ext_stop_site(cook,e)->fold_hostsrequest_infoe(Ocsigen_cookie_map.add_multicookcookies_to_set)l(* try next site *)|Ext_stop_all(_cook,e)->Lwt.fail(Ocsigen_http_error(cookies_to_set,e))|Ext_continue_with(_,cook,e)->fold_hostsrequest_infoe(Ocsigen_cookie_map.add_multicookcookies_to_set)l|Ext_retry_with(request2,cook)->fold_hosts_limited(get_hosts())(Ocsigen_cookie_map.add_multicookcookies_to_set)request2.request_info(* retry all *)|Ext_sub_result_sr->assertfalse)|(h,_,_)::l->Lwt_log.ign_info_f~section"host = %a does not match %a"(fun()->string_of_host_option)host(fun()->string_of_host)h;fold_hostsrequest_infoprev_errcookies_to_setlandfold_hosts_limitedsitescookies_to_setrequest_info=Ocsigen_request.incr_triesrequest_info;ifOcsigen_request.triesrequest_info>Ocsigen_config.get_maxretries()thenLwt.failOcsigen_Looping_requestelsefold_hostsrequest_info`Not_foundcookies_to_setsitesinfold_hosts_limited(get_hosts())previous_cookiesrequest_infoletget_number_of_connected=Ocsigen_cohttp.get_number_of_connected(* user directories *)exceptionNoSuchUsertypeud_string=Nodirofstring|Withdirofstring*string*stringletuser_dir_regexp=Ocsigen_lib.Netstring_pcre.regexp"(.*)\\$u\\(([^\\)]*)\\)(.*)"letparse_user_dirs=matchPcre.full_split~rex:user_dir_regexp~max:(-1)swith|[Pcre.Delim_;Pcre.Group(1,s1);Pcre.Group(2,u);Pcre.Group(3,s2)]->Withdir(s1,u,s2)|_->Nodirsletreplace_user_dirregexpdestpathstring=matchdestwith|Nodirdest->Ocsigen_lib.Netstring_pcre.global_replaceregexpdestpathstring|Withdir(s1,u,s2)->trylets1=Ocsigen_lib.Netstring_pcre.global_replaceregexps1pathstringinletu=Ocsigen_lib.Netstring_pcre.global_replaceregexpupathstringinlets2=Ocsigen_lib.Netstring_pcre.global_replaceregexps2pathstringinletuserdir=(Unix.getpwnamu).Unix.pw_dirinLwt_log.ign_info_f~section"User %s"u;s1^userdir^s2withNot_found->Lwt_log.ign_info_f~section"No such user %s"u;raiseNoSuchUserexceptionNot_concernedlet(>|!)vf=matchvwith|None->raiseNot_concerned|Somev->fvletfind_redirectionregexpfull_urldestr=iffull_urlthenOcsigen_request.hostr>|!funhost->letpath=letfull_path=Ocsigen_request.path_stringrinmatchOcsigen_request.queryrwith|None->full_path|Someg->full_path^"?"^ginletpath=Url.make_absolute_url~https:(Ocsigen_request.sslr)~host~port:(Ocsigen_request.portr)("/"^path)inOcsigen_lib.Netstring_pcre.string_matchregexppath0>|!fun_->(* Matching regexp found! *)Ocsigen_lib.Netstring_pcre.global_replaceregexpdestpathelseletpath=letsub_path=Ocsigen_request.sub_path_stringrinmatchOcsigen_request.queryrwith|None->sub_path|Someg->sub_path^"?"^ginOcsigen_lib.Netstring_pcre.string_matchregexppath0>|!fun_->(* Matching regexp found! *)Ocsigen_lib.Netstring_pcre.global_replaceregexpdestpath