12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451# 1 "src/lib/eliom_common.server.ml"(* Ocsigen
* http://www.ocsigen.org
* Copyright (C) 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.
*)openEliom_libincludeEliom_common_baseexceptionEliom_Session_expiredexceptionEliom_there_are_unregistered_servicesof(stringlist*stringlistlist*na_key_servlist)exceptionEliom_error_while_loading_siteofstringexceptionEliom_do_redirectionofstringexceptionEliom_do_half_xhr_redirectionofstringtype'atenable_value=<get:'a;set:?override_tenable:bool->'a->unit>lettenable_value~namev=objectvalmutablevalue=vvalmutabletenable=falsemethodget=valuemethodset?(override_tenable=false)v=if(nottenable)||override_tenablethen(value<-v;tenable<-override_tenable)elseLwt_log.ign_warning_f~section:Lwt_log.eliom"Ignored setting tenable value %S."nameend(*****************************************************************************)letdatacookiename="eliomdatasession|"letservicecookiename="eliomservicesession|"(* must not be a prefix of the following and vice versa (idem for data) *)letpersistentcookiename="eliompersistentsession|"(*****************************************************************************)(** The coservice does not exist any more *)leteliom_link_too_old:boolPolytables.key=Polytables.make_key()(** If present in request data, means that
the service session cookies does not exist any more.
The string lists are the list of names of expired sessions
*)leteliom_service_session_expired:(full_state_namelist*full_state_namelist)Polytables.key=Polytables.make_key()letfound_stop_key=Polytables.make_key()(*****************************************************************************)type'asession_cookie=SCNo_data|SCData_session_expired|SCof'atypecookie_exp=|CENothing(** nothing to set (keep current value) *)|CEBrowser(** expires at browser close *)|CESomeoffloat(** expiration date *)(* 2013-03-01 From now on, cookie expire 10 years after opening the session.
Before, it was when the browser was closed but we think it has no sense,
and many people do not understand why their session is closed, even if
the session duration on server side is long.
If you want this, you now have to set this manually.
*)letdefault_client_cookie_exp()=CESome(Unix.time()+.315532800.)typetimeout=|TGlobal(** see global setting *)|TNone(** explicitly set no timeout *)|TSomeoffloat(** timeout duration in seconds *)(* The table of tables for each session. Keys are hashes of cookies or group names *)moduleSessionCookies=Hashtbl.Make(structtypet=stringletequal=(=)lethash=Hashtbl.hashend)(* keys in tables are hashes of cookie values *)moduleHashed_cookies:sigtypetvalsha256:string->stringvalhash:string->tvalto_string:t->stringend=structtypet=stringletsha256c=letto_b64=Cryptokit.Base64.encode_compact()inCryptokit.transform_string to_b64@@Cryptokit.(hash_string(Hash.sha256())c)lethashc=(* To preserve compatibility, we only hash cookies that ends with an
'H'. This is the case for all new cookies (see Eliommod_cookies). *)ifc<>""&&c.[String.lengthc-1]='H'thensha256celsecletto_stringx=xend(* session groups *)type'asessgrp=string*cookie_level*(string,Ipaddr.t)leftright(* The full session group is the triple
(site_dir_string, scope, session group name).
The scope is the scope of group members (`Session by default).
If there is no session group,
we limit the number of sessions by IP address. *)typeperssessgrp=string(* same triple, marshaled *)letmake_persistent_full_group_name~cookie_levelsite_dir_string=function|None->None|Someg->Some(Marshal.to_string (site_dir_string,cookie_level,Leftg)[])letgetperssessgrpa:'asessgrp=Marshal.from_stringa0letstring_of_perssessgrp=id(* cookies information during page generation: *)type'aone_service_cookie_info={(* service sessions: *)sc_hvalue:Hashed_cookies.t(* hash of current value *);sc_set_value:stringoption(* new value to set *);sc_table:'aref(* service session table
ref towards cookie table
*);sc_timeout:timeoutref(* user timeout -
ref towards cookie table
*);sc_exp:floatoptionref(* expiration date ref
(server side) -
None = never
ref towards cookie table
*);sc_cookie_exp:cookie_expref(* cookie expiration date to set *);sc_session_group:cookie_levelsessgrpref(* session group *);mutablesc_session_group_node:stringOcsigen_cache.Dlist.node}typeone_data_cookie_info={(* in memory data sessions: *)dc_hvalue:Hashed_cookies.t(* hash of current value *);dc_set_value:stringoption(* new value to set *);dc_timeout:timeoutref(* user timeout -
ref towards cookie table
*);dc_exp:floatoptionref(* expiration date ref (server side) -
None = never
ref towards cookie table
*);dc_cookie_exp:cookie_expref(* cookie expiration date to set *);dc_session_group:cookie_levelsessgrpref(* session group *);mutabledc_session_group_node:stringOcsigen_cache.Dlist.node}typeone_persistent_cookie_info={pc_hvalue:Hashed_cookies.t(* hash of current value *);pc_set_value:stringoption(* new value to set *);pc_timeout:timeoutref(* user timeout *);pc_cookie_exp:cookie_expref(* cookie expiration date to set *);pc_session_group:perssessgrpoptionref(* session group *)}(*VVV heavy *)type'acookie_info1=(* service sessions: *)(stringoption(* value sent by the browser *)(* None = new cookie
(not sent by the browser) *)*'aone_service_cookie_infosession_cookieref)(* SCNo_data = the session has been closed
SCData_session_expired = the cookie has not been found in the table.
For both of them, ask the browser to remove the cookie.
*)(* This one is not lazy because we must check all service sessions
at each request to find the services *)Full_state_name_table.tref(* The key is the full session name *)*(* in memory data sessions: *)(stringoption(* value sent by the browser *)(* None = new cookie
(not sent by the browser) *)*one_data_cookie_infosession_cookieref)(* SCNo_data = the session has been closed
SCData_session_expired = the cookie has not been found in the table.
For both of them, ask the browser to remove the cookie.
*)Lazy.t(* Lazy because we do not want to ask the browser to unset the cookie
if the cookie has not been used, otherwise it is impossible to
write a message "Your session has expired" *)Full_state_name_table.tref(* The key is the full session name *)*(* persistent sessions: *)((string(* value sent by the browser *)*timeout(* timeout at the beginning of the request *)*floatoption(* (server side) expdate
at the beginning of the request
None = no exp *)*perssessgrpoption)(* session group at beginning of request *)option(* None = new cookie
(not sent by the browser) *)*one_persistent_cookie_infosession_cookieref)(* SCNo_data = the session has been closed
SCData_session_expired = the cookie has not been found in the table.
For both of them, ask the browser to remove the cookie.
*)Lwt.tLazy.tFull_state_name_table.treftype'acookie_info='acookie_info1(* unsecure *)*'acookie_info1(* secure *)moduleService_cookie=struct(* non persistent cookies for services *)type'at={full_state_name:full_state_name;session_table:'a;expiry:floatoptionref;timeout:timeoutref;session_group:cookie_levelsessgrpref;session_group_node:stringOcsigen_cache.Dlist.node}type'atable='atSessionCookies.t(* the table contains:
- the table of services
- the expiration date (by timeout), changed at each access to the table
(float option) None -> no expiration
- the timeout for the user (float option option) None -> see global config
Some None -> no timeout
- the group to which belongs the session
*)endmoduleData_cookie=struct(* non persistent cookies for in-memory data *)typet={full_state_name:full_state_name;expiry:floatoptionref;timeout:timeoutref;session_group:cookie_levelsessgrpref;session_group_node:stringOcsigen_cache.Dlist.node}typetable=tSessionCookies.tend(*****************************************************************************)letipv4mask=ref16letipv6mask=ref56letget_mask4m=matchfstmwithSomem->m|None->!ipv4maskletget_mask6m=matchfstmwithSomem->m|None->!ipv6maskletnetwork_of_ipkmask4mask6=matchkwith|Ipaddr.V4ip->Ipaddr.(V4V4.Prefix.(network(makemask4ip)))|Ipaddr.V6ip->Ipaddr.(V6V6.Prefix.(network(makemask6ip)))moduleNet_addr_Hashtbl:sigtypekey=Ipaddr.ttype'atvalcreate:int->'atvaladd:intoption*'bb->intoption*'bb->'at->key->'a->unitvalremove:intoption*'bb->intoption*'bb->'at->key->unitvalfind:intoption*'bb->intoption*'bb->'at->key->'aend=(* keys are IP address modulo "network equivalence" *)structincludeHashtbl.Make(structtypet=Ipaddr.tletequalab=Ipaddr.compareab=0lethash=Hashtbl.hashend)letaddm4m6tkv=addt(network_of_ipk(get_mask4m4)(get_mask6m6))vletremovem4m6tk=removet(network_of_ipk(get_mask4m4)(get_mask6m6))letfindm4m6tk=findt(network_of_ipk(get_mask4m4)(get_mask6m6))endmoduleServ_Table=Map.Make(structtypet=page_table_keyletcompare=compareend)moduleNAserv_Table=Map.Make(structtypet=na_key_servletcompare=compareend)typenode_info={ni_id:node_ref;mutableni_sent:bool}moduleHier_set=String.Settypeomitpersistentstorage_rule=HeaderRuleofOcsigen_header.Name.t*Re.retypeserver_params={sp_request:Ocsigen_extensions.request;sp_si:sess_info;sp_sitedata:sitedata(* data for the whole site *);sp_cookie_info:tablescookie_info;sp_tab_cookie_info:tablescookie_info;mutablesp_user_cookies:Ocsigen_cookie_map.t;(* cookies (un)set by the user during service *)mutablesp_user_tab_cookies:Ocsigen_cookie_map.t;mutablesp_client_appl_name:stringoption;(* The application name,
as sent by the browser *)sp_suffix:Url.pathoption(* suffix *);sp_full_state_name:full_state_nameoption(* the name of the session
to which belong the service that answered
(if it is a session service) *);sp_client_process_info:client_process_info}andpage_table=page_table_contentServ_Table.tandpage_table_content=[`Ptcof(page_tableref*page_table_key,na_key_serv)leftrightOcsigen_cache.Dlist.nodeoption*(server_params,Ocsigen_response.t)servicelist]andnaservice_table_content=int(* generation (= number of reloads of sites
after which that service has been created) *)*intrefoption(* max_use *)*(float*floatref)option(* timeout and expiration date *)*(server_params->Ocsigen_response.tLwt.t)*(page_tableref*page_table_key,na_key_serv)leftrightOcsigen_cache.Dlist.nodeoption(* for limitation of number of dynamic coservices *)andnaservice_table=AVide|ATableofnaservice_table_contentNAserv_Table.tandtables={mutabletable_services:(int(* generation *)*int(* priority *)*page_tabledircontentref)list;table_naservices:naservice_tableref;(* ref, and not mutable field because it simpler to use
recursively with Dir of dircontent ref *)(* Information for the GC: *)mutabletable_contains_services_with_timeout:bool;(* true if dircontent contains services with timeout *)mutabletable_contains_naservices_with_timeout:bool;(* true if naservice_table contains services with timeout *)mutablecsrf_get_or_na_registration_functions:(sp:server_params->string)Int.Table.t;mutablecsrf_post_registration_functions:(sp:server_params->att_key_serv->string)Int.Table.t;(* These two table are used for CSRF safe services:
We associate to each service unique id the function that will
register a new anonymous coservice each time we create a link or form.
Attached POST coservices may have both a GET and POST
registration function. That's why there are two tables.
The functions associated to each service may be different for
each session. That's why we use these table, and not a field in
the service record.
*)service_dlist_add:?sp:server_params->(page_tableref*page_table_key,na_key_serv)leftright->(page_tableref*page_table_key,na_key_serv)leftrightOcsigen_cache.Dlist.node(* We use a dlist for limiting the number of dynamic
anonymous coservices in each table (and avoid DoS). There
is one dlist for each session, and one for each IP in
global tables. The dlist parameter is the table and
coservice number for attached coservices, and the
coservice number for non-attached ones. *)}andsitedata={site_dir:Url.path;site_dir_string:string;config_info:Ocsigen_extensions.config_info;default_links_xhr:booltenable_value;(* Timeouts:
- default for site (browser sessions)
- default for site (tab sessions)
- then default for each full session name
The booleans means "has been set from config file"
*)mutableservtimeout:(floatoption*bool)option*(floatoption*bool)option*(full_state_name*(floatoption*bool))list;mutabledatatimeout:(floatoption*bool)option*(floatoption*bool)option*(full_state_name*(floatoption*bool))list;mutableperstimeout:(floatoption*bool)option*(floatoption*bool)option*(full_state_name*(floatoption*bool))list;site_value_table:Polytables.t;(* table containing evaluated
lazy site values *)mutableregistered_scope_hierarchies:Hier_set.t;(* All services, and state data are stored in these tables,
for scopes session and client process.
The scope is registered in the full session name. *)global_services:tables;(* global service table *)session_services:tablesService_cookie.table;(* cookie table for services (tab and browser sessions) *)session_data:Data_cookie.table;(* cookie table for in memory session data
(tab and browser sessions)
contains the information about the cookie
(expiration, group ...). *)group_of_groups:[`Session_group]sessgrpOcsigen_cache.Dlist.t;(* Limitation of the number of groups per site *)mutableremove_session_data:string->unit;mutablenot_bound_in_data_tables:string->bool;mutableexn_handler:exn->Ocsigen_response.tLwt.t;mutableunregistered_services:Url.pathlist;mutableunregistered_na_services:na_key_servlist;mutablemax_volatile_data_sessions_per_group:int*bool;mutablemax_volatile_data_sessions_per_subnet:int*bool;mutablemax_volatile_data_tab_sessions_per_group:int*bool;mutablemax_service_sessions_per_group:int*bool;mutablemax_service_sessions_per_subnet:int*bool;mutablemax_service_tab_sessions_per_group:int*bool;mutablemax_persistent_data_sessions_per_group:intoption*bool;mutablemax_persistent_data_tab_sessions_per_group:intoption*bool;mutablemax_anonymous_services_per_session:int*bool;mutablemax_anonymous_services_per_subnet:int*bool;mutablesecure_cookies:bool;dlist_ip_table:dlist_ip_table;mutableipv4mask:intoption*bool;mutableipv6mask:intoption*bool;mutableapplication_script:bool(* defer *)*bool;(* async *)mutablecache_global_data:(stringlist*int)option;mutablehtml_content_type:stringoption;mutableignored_get_params:(string*Re.re)list;mutableignored_post_params:(string*Re.re)list;mutableomitpersistentstorage:omitpersistentstorage_rulelistoption}anddlist_ip_table=(page_tableref*page_table_key,na_key_serv)leftrightOcsigen_cache.Dlist.tNet_addr_Hashtbl.tletcreate_dlist_ip_table=Net_addr_Hashtbl.createletfind_dlist_ip_table:intoption*'b->intoption*'b->dlist_ip_table->Ipaddr.t->(page_tableref*page_table_key,na_key_serv)leftrightOcsigen_cache.Dlist.t=Net_addr_Hashtbl.find(*****************************************************************************)(*****************************************************************************)letmake_full_cookie_namecookieprefix{user_scope;secure;site_dir_str}=letscope_hier=scope_hierarchy_of_user_scopeuser_scopeinletsecure=ifsecurethen"S|"else"|"inlethier1,hiername=matchscope_hierwith|User_hierhiername->"||",hiername|Default_ref_hier->"|ref|",""|Default_comet_hier->"|comet|",""inString.concat""[cookieprefix;secure;site_dir_str;hier1;hiername]letmake_full_state_name2site_dir_strsecure~(scope:[<user_scope]):full_state_name=(* The information in the cookie name, without the kind of session *){user_scope=(scope:>user_scope);secure;site_dir_str}letmake_full_state_name~sp~secure~(scope:[<user_scope])=make_full_state_name2sp.sp_sitedata.site_dir_stringsecure~scopeletget_cookie_infosp=function|`Session->sp.sp_cookie_info|`Client_process->sp.sp_tab_cookie_infotypeinfo={request:Ocsigen_extensions.request;session_info:sess_info;all_cookie_info:tablescookie_info;tab_cookie_info:tablescookie_info;user_tab_cookies:Ocsigen_cookie_map.t}(*****************************************************************************)(** Create server parameters record *)letmake_server_paramssitedata({request=ri;session_info=si;_}asinfo)suffixfull_state_name=letappl_name=trySome(Ocsigen_cookie_map.Map_inner.findappl_name_cookie_namesi.si_tab_cookies)(* It is an XHR from the client application, or an internal form *)withNot_found->Noneinletcpi=matchsi.si_client_process_infowith|Somecpi->cpi|None->letrequest_info=ri.Ocsigen_extensions.request_infoin{cpi_ssl=Ocsigen_request.sslrequest_info;cpi_hostname=Ocsigen_extensions.get_hostnameri;cpi_server_port=Ocsigen_extensions.get_portri;cpi_original_full_path=Ocsigen_request.original_full_pathrequest_info}in{sp_request=ri;sp_si=si;sp_sitedata=sitedata;sp_cookie_info=info.all_cookie_info;sp_tab_cookie_info=info.tab_cookie_info;sp_user_cookies=Ocsigen_cookie_map.empty;sp_user_tab_cookies=info.user_tab_cookies;sp_client_appl_name=appl_name;sp_suffix=suffix;sp_full_state_name=full_state_name;sp_client_process_info=cpi}letsp_key=Lwt.new_key()letget_sp_option()=Lwt.getsp_keyletget_sp()=matchLwt.getsp_keywith|Somesp->sp|None->letmsg="This function cannot be called here because it needs information about the request or the site."infailwith@@String.concat"\n"@@(msg::Eliom_common_base.backtrace_lwt2)letsp_of_optionsp=matchspwithNone->get_sp()|Somesp->sp(*****************************************************************************)(* Scope registration *)(*****************************************************************************)letglobal_scope:[>global_scope]=`Globalletsite_scope:[>site_scope]=`Siteletdefault_group_scope:[>session_group_scope]=`Session_groupDefault_ref_hierletdefault_session_scope:[>session_scope]=`SessionDefault_ref_hierletdefault_process_scope:[>client_process_scope]=`Client_processDefault_ref_hierletcomet_client_process_scope:[>client_process_scope]=`Client_processDefault_comet_hierletrequest_scope:[>request_scope]=`Requestletregistered_scope_hierarchies=refHier_set.emptyletregister_scope_hierarchy(name:string)=matchget_sp_option()with|None->ifHier_set.memname!registered_scope_hierarchiesthenfailwith(Printf.sprintf"the scope hierarchy %s has already been registered"name)elseregistered_scope_hierarchies:=Hier_set.addname!registered_scope_hierarchies|Somesp->ifHier_set.memname!registered_scope_hierarchies||Hier_set.memnamesp.sp_sitedata.registered_scope_hierarchiesthenfailwith(Printf.sprintf"the scope hierarchy %s has already been registered"name)elsesp.sp_sitedata.registered_scope_hierarchies<-Hier_set.addnamesp.sp_sitedata.registered_scope_hierarchiesletcreate_scope_hierarchyname:scope_hierarchy=register_scope_hierarchyname;User_hiernameletlist_scope_hierarchies()=letsp=get_sp()inDefault_comet_hier::Default_ref_hier::(List.map(funs->User_hiers)(Hier_set.elements!registered_scope_hierarchies)@List.map(funs->User_hiers)(Hier_set.elementssp.sp_sitedata.registered_scope_hierarchies))(*****************************************************************************)(* The current registration directory *)letabsolute_change_sitedata,get_current_sitedata,end_current_sitedata=letf2:sitedatalistref=ref[]inletpopf2()=match!f2with_::t->f2:=t|[]->f2:=[]in((funsitedata->f2:=sitedata::!f2)(* absolute_change_sitedata *),(fun()->match!f2with|[]->raise(Eliom_site_information_not_available"get_current_sitedata")|sd::_->sd)(* get_current_sitedata *),fun()->popf2()(* end_current_sitedata *))(* Warning: these functions are used only during the initialisation
phase, which is not threaded ... That's why it works, but ...
it is not really clean ... public registration relies on this
directory (defined for each site in the config file)
*)(*****************************************************************************)letadd_unregisteredsitedataa=sitedata.unregistered_services<-a::sitedata.unregistered_servicesletadd_unregistered_nasitedataa=sitedata.unregistered_na_services<-a::sitedata.unregistered_na_servicesletremove_unregisteredsitedataa=sitedata.unregistered_services<-List.remove_first_if_anyasitedata.unregistered_servicesletremove_unregistered_nasitedataa=sitedata.unregistered_na_services<-List.remove_first_if_anyasitedata.unregistered_na_servicesletverify_all_registeredsitedata=matchsitedata.unregistered_services,sitedata.unregistered_na_serviceswith|[],[]->()|l1,l2->raise(Eliom_there_are_unregistered_services(sitedata.site_dir,l1,l2))letduring_eliom_module_loading,begin_load_eliom_module,end_load_eliom_module=letduring_eliom_module_loading_=reffalsein((fun()->!during_eliom_module_loading_),(fun()->during_eliom_module_loading_:=true),fun()->during_eliom_module_loading_:=false)letglobal_register_allowed()=ifOcsigen_extensions.during_initialisation()&&during_eliom_module_loading()thenSomeget_current_sitedataelseNoneletget_site_data()=matchget_sp_option()with|Somesp->sp.sp_sitedata|None->ifduring_eliom_module_loading()thenget_current_sitedata()elsefailwith"get_site_data"(*****************************************************************************)(* Lazy site value: each site have a different value *)(* Evaluated values are never collected by the GC, the table always
keeps a reference on it. *)(* there is no test for cycles *)type'alazy_site_value={lazy_sv_fun:unit->'a;lazy_sv_key:'aPolytables.key}letforce_lazy_site_valuev=letsitedata=matchget_sp_option()with|Somesp->sp.sp_sitedata|None->(matchglobal_register_allowed()with|Somef->f()|None->raise(Eliom_site_information_not_available"force_lazy_site_value"))intryPolytables.get~table:sitedata.site_value_table~key:v.lazy_sv_keywithNot_found->letvalue=v.lazy_sv_fun()inPolytables.set~table:sitedata.site_value_table~key:v.lazy_sv_key~value;valueletlazy_site_value_from_funf={lazy_sv_key=Polytables.make_key();lazy_sv_fun=f}(*****************************************************************************)(*****************************************************************************)(* The table of dynamic pages for each virtual server, and naservices *)(* Each node contains either a list of nodes (case directory)
or a table of "answers" (functions that will generate the page) *)letempty_page_table()=Serv_Table.emptyletempty_naservice_table()=AVideletservice_tables_are_emptyt=!(t.table_naservices)=AVide&&(* !(t.table_services) = [] <---- probably enough? *)List.for_all(fun(_,_,r)->!r=Vide)t.table_servicesletremove_naservice_tableatk=matchatwithAVide->AVide|ATablet->ATable(NAserv_Table.removekt)letdlist_finaliserna_table_refnode=(* If the node disappears from the dlist,
we remove the service from the service table *)matchOcsigen_cache.Dlist.valuenodewith|Left(page_table_ref,page_table_key)->page_table_ref:=Serv_Table.removepage_table_key!page_table_ref|Rightna_key_serv->na_table_ref:=remove_naservice_table!na_table_refna_key_servletdlist_finaliser_ipsitedataipna_table_refnode=dlist_finaliserna_table_refnode;matchOcsigen_cache.Dlist.list_ofnodewith|Somecl->(ifOcsigen_cache.Dlist.sizecl=1thentryNet_addr_Hashtbl.removesitedata.ipv4masksitedata.ipv6masksitedata.dlist_ip_tableipwithNot_found->())|None->()letadd_dlist_dlistv=ignore(Ocsigen_cache.Dlist.addvdlist);matchOcsigen_cache.Dlist.newestdlistwith|Somea->a|None->assertfalseletempty_tablesmaxforsession=lett1=[]inlett2=ref(empty_naservice_table())in{table_services=t1;table_naservices=t2;table_contains_services_with_timeout=false;table_contains_naservices_with_timeout=false;csrf_get_or_na_registration_functions=Int.Table.empty;csrf_post_registration_functions=Int.Table.empty;service_dlist_add=(ifforsessionthen(letdlist=Ocsigen_cache.Dlist.createmaxinOcsigen_cache.Dlist.set_finaliser_before(dlist_finalisert2)dlist;fun?sp:_v->add_dlist_dlistv)elsefun?spv->letip,max,sitedata=matchspwith|None->((Ipaddr.(V6V6.localhost),max,matchglobal_register_allowed()with|None->failwith"global tables created outside initialisation"|Someget->get()))|Somesp->(Ocsigen_request.remote_ip_parsedsp.sp_request.Ocsigen_extensions.request_info,fstsp.sp_sitedata.max_anonymous_services_per_subnet,sp.sp_sitedata)inletdlist=tryNet_addr_Hashtbl.findsitedata.ipv4masksitedata.ipv6masksitedata.dlist_ip_tableipwithNot_found->letdlist=Ocsigen_cache.Dlist.createmaxinNet_addr_Hashtbl.addsitedata.ipv4masksitedata.ipv6masksitedata.dlist_ip_tableipdlist;Ocsigen_cache.Dlist.set_finaliser_before(dlist_finaliser_ipsitedataipt2)dlist;dlistinadd_dlist_dlistv)}letnew_service_session_tablessitedata=empty_tables(fstsitedata.max_anonymous_services_per_session)trueletget_mask4sitedata=get_mask4sitedata.ipv4maskletget_mask6sitedata=get_mask6sitedata.ipv6mask(*****************************************************************************)openLwt(* The cookie name is
sessionkind|S?|sitedirstring|"ref" ou "comet" ou ""|hiername
*)letfull_state_name_of_cookie_namecookie_levelcookiename=let_pref,cookiename=Ocsigen_lib.String.sep'|'cookienameinletsecure,cookiename=Ocsigen_lib.String.sep'|'cookienameinletsite_dir_str,cookiename=Ocsigen_lib.String.sep'|'cookienameinlethier1,hiername=Ocsigen_lib.String.sep'|'cookienameinletsecure=secure="S"inletsc_hier=matchhier1with|""->Eliom_common_base.User_hierhiername|"ref"->Eliom_common_base.Default_ref_hier|"comet"->Eliom_common_base.Default_comet_hier|_->raiseNot_foundinletuser_scope=matchcookie_levelwith|`Session->`Sessionsc_hier|`Client_process->`Client_processsc_hierin{user_scope;secure;site_dir_str}letgetcookiessecurecookie_levelcookienameprefcookies=letlength=String.lengthcookienameprefinletlast=length-1inOcsigen_cookie_map.Map_inner.fold(funnamevaluebeg->ifString.first_diffcookienameprefname0last=lengththentryletexpcn=full_state_name_of_cookie_namecookie_levelnameinifexpcn.secure=securethenFull_state_name_table.addexpcnvaluebegelsebegwithNot_found->begelsebeg)cookiesFull_state_name_table.empty(* After an action, we do not take into account actual get params,
but these ones: *)leteliom_params_after_action=Polytables.make_key()(* After an action, we get tab_cookies info from rc: *)lettab_cookie_action_info_key=Polytables.make_key()[@@@warning"-39"]typecpi=client_process_info={cpi_ssl:bool;cpi_hostname:string;cpi_server_port:int;cpi_original_full_path:stringlist}[@@derivingjson][@@@warning"+39"]letmatches_regexpname(_,re)=trylet_=Re.execrenameintruewithNot_found->falseletmatches_regexpsregexps(name,_)=List.exists(matches_regexpname)regexpsletget_session_info~sitedata~reqprevious_extension_err=letreq_whole=reqandri=req.Ocsigen_extensions.request_infoandci=req.Ocsigen_extensions.request_configinletrc=Ocsigen_request.request_cacheriinletno_post_param,p=matchOcsigen_request.post_paramsrici.Ocsigen_extensions.uploaddirci.Ocsigen_extensions.maxuploadfilesizewith|None->true,Lwt.return[]|Somev->false,vinletno_file_param,file_params=matchOcsigen_request.filesrici.Ocsigen_extensions.uploaddirci.Ocsigen_extensions.maxuploadfilesizewith|None->true,Lwt.return[]|Somev->false,vinlet%lwtpost_params=pinletprevious_tab_cookies_info,tab_cookies,post_params=trylettci,utc,tc=Polytables.get~table:rc~key:tab_cookie_action_info_keyinPolytables.remove~table:rc~key:tab_cookie_action_info_key;Some(tci,utc),tc,post_paramswithNot_found->lettab_cookies,post_params=try(* Tab cookies are found in HTTP headers,
but also sometimes in POST params (when we do not want to do an XHR
because we want to stop the client side process).
It should never be both.
*)lettc,pp=List.assoc_removetab_cookies_param_namepost_paramsinlettc=[%of_json:(string*string)list]tcin(List.fold_left(funt(k,v)->Ocsigen_cookie_map.Map_inner.addkvt)Ocsigen_cookie_map.Map_inner.emptytc,pp)(*Marshal.from_string (Ocsigen_lib.decode tc) 0, pp*)withNot_found->(matchOcsigen_request.headerri(Ocsigen_header.Name.of_stringtab_cookies_header_name)with|Sometc->lettc=[%of_json:(string*string)list]tcin(List.fold_left(funt(k,v)->Ocsigen_cookie_map.Map_inner.addkvt)Ocsigen_cookie_map.Map_inner.emptytc,post_params)|None->Ocsigen_cookie_map.Map_inner.empty,post_params)inNone,tab_cookies,post_paramsinletcpi=matchOcsigen_request.headerri(Ocsigen_header.Name.of_stringtab_cpi_header_name)with|Somecpi->Some([%of_json:cpi]cpi)|None->Noneinletepd=lazy(matchOcsigen_request.headerri(Ocsigen_header.Name.of_stringexpecting_process_page_name)with|Someepd->[%of_json:bool]epd|None->false)inletpost_params,get_params,to_be_considered_as_get=letg=Ocsigen_request.get_params_flatriintry([],g@snd(List.assoc_removeto_be_considered_as_get_param_namepost_params),true)(* It was a POST request to be considered as GET *)withNot_found->post_params,g,falsein(*204FORMS* old implementation of forms with 204 and change_page_event
let get_params, internal_form =
try
(snd (List.assoc_remove internal_form_full_name get_params),
true)
with Not_found -> (get_params, false)
in
*)letget_params0=get_paramsinletpost_params0=post_paramsinlet%lwtfile_params0=file_paramsinlet(get_params,post_params,file_params,(all_get_params,all_post_params,all_file_params,nl_get_params,nl_post_params,nl_file_params,all_get_but_nl(*204FORMS*, internal_form *),ignored_get,ignored_post))=try(get_params,post_params,file_params0,Polytables.get~table:(Ocsigen_request.request_cacheri)~key:eliom_params_after_action)withNot_found->letnl_get_params,get_params=split_nl_prefix_paramget_params0inletnl_post_params,post_params=split_nl_prefix_parampost_params0inletnl_file_params,file_params=split_nl_prefix_paramfile_params0inletignored_get,get_params=List.partition(matches_regexpssitedata.ignored_get_params)get_paramsinletignored_post,post_params=List.partition(matches_regexpssitedata.ignored_post_params)post_paramsinletall_get_but_nl=get_paramsin(get_params,post_params,file_params,(get_params0,(ifno_post_paramthenNoneelseSomepost_params0),(ifno_file_paramthenNoneelseSomefile_params0),nl_get_params,nl_post_params,nl_file_params,all_get_but_nl(*204FORMS*, internal_form *),ignored_get,ignored_post))inletbrowser_cookies=matchOcsigen_request.headerri(Ocsigen_header.Name.of_stringcookie_substitutes_header_name)with|Sometc->List.fold_left(funt(k,v)->Ocsigen_cookie_map.Map_inner.addkvt)Ocsigen_cookie_map.Map_inner.empty([%of_json:(string*string)list]tc)|None->Ocsigen_request.cookiesriinletdata_cookies=getcookiesfalse`Sessiondatacookienamebrowser_cookiesinletservice_cookies=getcookiesfalse`Sessionservicecookienamebrowser_cookiesinletpersistent_cookies=getcookiesfalse`Sessionpersistentcookienamebrowser_cookiesinletsecure_cookie_info=letsdata_cookies=getcookiestrue`Sessiondatacookienamebrowser_cookiesinletsservice_cookies=getcookiestrue`Sessionservicecookienamebrowser_cookiesinletspersistent_cookies=getcookiestrue`Sessionpersistentcookienamebrowser_cookiesinsservice_cookies,sdata_cookies,spersistent_cookiesinlet(naservice_info,(get_state,post_state),(get_params,other_get_params),na_get_params,post_params)=letpost_naservice_name,na_post_params=tryletn,pp=List.assoc_removenaservice_numpost_paramsinRNa_post'n,ppwithNot_found->(tryletn,pp=List.assoc_removenaservice_namepost_paramsinRNa_post_n,ppwithNot_found->RNa_no,[])inmatchpost_naservice_namewith|RNa_post__|RNa_post'_->(* POST non attached coservice *)(post_naservice_name,(RAtt_no,RAtt_no),([],get_params),lazy(try(trynaservice_name,List.assocnaservice_nameget_paramswithNot_found->naservice_num,List.assocnaservice_numget_params)::fst(split_prefix_paramna_co_param_prefixget_params)withNot_found->[]),na_post_params)|_->(letget_naservice_name,na_name_num,(na_get_params,other_get_params)=tryletn,gp=List.assoc_removenaservice_numget_paramsin(RNa_get'n,[naservice_num,n],split_prefix_paramna_co_param_prefixgp)withNot_found->(tryletn,gp=List.assoc_removenaservice_nameget_paramsin(RNa_get_n,[naservice_name,n],split_prefix_paramna_co_param_prefixgp)withNot_found->RNa_no,[],([],get_params))inmatchget_naservice_namewith|RNa_get__|RNa_get'_->(* GET non attached coservice *)(get_naservice_name,(RAtt_no,RAtt_no),(na_get_params,other_get_params),lazy(na_name_num@na_get_params),[])(* Not possible to have POST parameters
without naservice_num
if there is a GET naservice_num
*)|_->letpost_state,post_params=trylets,pp=List.assoc_removepost_numstate_param_namepost_paramsinRAtt_anons,ppwithNot_found->(trylets,pp=List.assoc_removepost_state_param_namepost_paramsinRAtt_nameds,ppwithNot_found->RAtt_no,post_params)inletget_state,(get_params,other_get_params)=trylets,gp=List.assoc_removeget_numstate_param_nameget_paramsinRAtt_anons,split_prefix_paramco_param_prefixgpwithNot_found->(trylets,gp=List.assoc_removeget_state_param_nameget_paramsinRAtt_nameds,split_prefix_paramco_param_prefixgpwithNot_found->RAtt_no,(get_params,[]))in(RNa_no,(get_state,post_state),(get_params,other_get_params),lazy(na_name_num@na_get_params),post_params))inletpersistent_nl_get_params=lazy(String.Table.fold(funkat->ifnl_is_persistentkthenString.Table.addkatelset)nl_get_paramsString.Table.empty)inletdata_cookies_tab=getcookiesfalse`Client_processdatacookienametab_cookiesinletservice_cookies_tab=getcookiesfalse`Client_processservicecookienametab_cookiesinletpersistent_cookies_tab=getcookiesfalse`Client_processpersistentcookienametab_cookiesinletsecure_cookie_info_tab=letsdata_cookies=getcookiestrue`Client_processdatacookienametab_cookiesinletsservice_cookies=getcookiestrue`Client_processservicecookienametab_cookiesinletspersistent_cookies=getcookiestrue`Client_processpersistentcookienametab_cookiesinsservice_cookies,sdata_cookies,spersistent_cookiesinletri,sess=(*VVV 2011/02/15 TODO: I think we'd better not change ri here.
Keep ri for original values and use si for Eliom's values?
*)(Ocsigen_request.updateri?meth:(ifOcsigen_request.methri=`HEAD||to_be_considered_as_getthenSome`GETelseNone(* Here we modify ri, instead of putting service parameters in
si. Thus it works better after actions: the request can be
taken by other extensions, with new parameters. Initial
parameters are kept in si. *))~get_params_flat:get_params?post_data:(ifno_post_paramthenNoneelseSome(Some(post_params,file_params))),{si_service_session_cookies=service_cookies;si_data_session_cookies=data_cookies;si_persistent_session_cookies=persistent_cookies;si_secure_cookie_info=secure_cookie_info;si_service_session_cookies_tab=service_cookies_tab;si_data_session_cookies_tab=data_cookies_tab;si_persistent_session_cookies_tab=persistent_cookies_tab;si_secure_cookie_info_tab=secure_cookie_info_tab;si_tab_cookies=tab_cookies;si_nonatt_info=naservice_info;si_state_info=get_state,post_state;si_other_get_params=other_get_params;si_all_get_params=all_get_params;si_all_post_params=all_post_params;si_all_file_params=all_file_params;si_previous_extension_error=previous_extension_err;si_na_get_params=na_get_params;si_nl_get_params=nl_get_params;si_nl_post_params=nl_post_params;si_nl_file_params=nl_file_params;si_persistent_nl_get_params=persistent_nl_get_params;si_all_get_but_nl=all_get_but_nl;si_all_get_but_na_nl=lazy(remove_na_prefix_paramsall_get_but_nl);si_ignored_get_params=ignored_get;si_ignored_post_params=ignored_post;si_client_process_info=cpi;si_expect_process_data=epd(*204FORMS* si_internal_form= internal_form; *)})inLwt.return({req_wholewithOcsigen_extensions.request_info=ri},sess,previous_tab_cookies_info)exceptionEliom_retry_withofinfo(*****************************************************************************)moduleOmit_persistent_storage=structletcheck_if_omitting_storage()=matchget_sp_option()with|Some{sp_request;sp_sitedata={omitpersistentstorage=Somerules;_};_}->letapply_rule=function|HeaderRule(header_name,regexp)->(matchOcsigen_request.headersp_request.Ocsigen_extensions.request_infoheader_namewith|None->false(* no User-Agent header *)|Someheader_value->Re.execpregexpheader_value)inList.for_allapply_rulerules|_->falseletnot_if_omitting_storagef=ifcheck_if_omitting_storage()thenLwt.return_unitelsef()endmoduleOcsipersist=structincludeOcsipersistmodulePolymorphic=structincludeOcsipersist.Polymorphicletaddtablekeyvalue=Omit_persistent_storage.not_if_omitting_storage(fun()->addtablekeyvalue)letremovetablekey=Omit_persistent_storage.not_if_omitting_storage(fun()->removetablekey)letreplace_if_existstablekeyvalue=Omit_persistent_storage.not_if_omitting_storage(fun()->replace_if_existstablekeyvalue)endmoduleStore=structincludeOcsipersist.Storeletsetpvvalue=Omit_persistent_storage.not_if_omitting_storage(fun()->setpvvalue)endmoduleFunctorial=structincludeOcsipersist.FunctorialmoduleTable(T:sigvalname:stringend)(Key:COLUMN)(Value:COLUMN)=structincludeTable(T)(Key)(Value)letaddkeyvalue=Omit_persistent_storage.not_if_omitting_storage(fun()->addkeyvalue)letremovekey=Omit_persistent_storage.not_if_omitting_storage(fun()->removekey)letreplace_if_existskeyvalue=Omit_persistent_storage.not_if_omitting_storage(fun()->replace_if_existskeyvalue)letmodify_optkeyf=Omit_persistent_storage.not_if_omitting_storage(fun()->modify_optkeyf)endendend(* keeping track of all the persistent tables *)modulePersistent_tables=structletpolymorphic_tables=ref[]letfunctorial_tables=ref[]letcreatename=polymorphic_tables:=name::!polymorphic_tables;Ocsipersist.Polymorphic.open_tablenameletadd_functorial_tablet=functorial_tables:=t::!functorial_tables(** removes the entry from all opened tables *)letremove_key_from_all_tableskey=(* doesn't remove entry from Persistent_cookies_expiry_dates; not a problem *)Lwt_list.iter_s(fun(moduleT:Ocsipersist.TABLEwithtypekey=string)->T.removekey)!functorial_tables>>=fun()->Lwt_list.iter_s(* could be replaced by iter_p *)(funt->Ocsipersist.Polymorphic.open_tablet>>=funtable->Ocsipersist.Polymorphic.removetablekey>>=Lwt.pause)!polymorphic_tablesletnumber_of_tables()=List.length!polymorphic_tables+List.length!functorial_tablesletnumber_of_table_elements()=Lwt_list.map_s(funt->Ocsipersist.Polymorphic.open_tablet>>=funtable->Ocsipersist.Polymorphic.lengthtable>>=fune->Lwt.return(t,e))!polymorphic_tables>>=funpolymorphic_counts->Lwt_list.map_s(fun(moduleT:Ocsipersist.TABLEwithtypekey=string)->T.length()>>=funn->Lwt.return(T.name,n))!functorial_tables>>=funfunctorial_counts->Lwt.return@@polymorphic_counts@functorial_countsend(**** Wrapper type shared by client/server side ***)type'awrapper='aEliom_wrap.wrapperletmake_wrapperf=Eliom_wrap.create_wrapperfletempty_wrapper()=Eliom_wrap.empty_wrappertypeunwrap_id=Eliom_wrap.unwrap_idtypeunwrapper=Eliom_wrap.unwrapperletmake_unwrapper=Eliom_wrap.create_unwrapperletempty_unwrapper=Eliom_wrap.empty_unwrapperletreact_up_unwrap_id:unwrap_id=Eliom_wrap.id_of_intreact_up_unwrap_id_intletreact_down_unwrap_id:unwrap_id=Eliom_wrap.id_of_intreact_down_unwrap_id_intletsignal_down_unwrap_id:unwrap_id=Eliom_wrap.id_of_intsignal_down_unwrap_id_intletcomet_channel_unwrap_id:unwrap_id=Eliom_wrap.id_of_intcomet_channel_unwrap_id_intletbus_unwrap_id:unwrap_id=Eliom_wrap.id_of_intbus_unwrap_id_int(* HACK: Remove the 'nl_get_appl_parameter' used to avoid confusion
between XHR and classical request in App. *)letpatch_request_info({Ocsigen_extensions.request_info;_}asr)=letu=Ocsigen_request.urirequest_infoinmatchUri.get_query_paramunl_get_appl_parameterwith|Some_->{rwithOcsigen_extensions.request_info=(letget_params_flat=List.remove_assocnl_get_appl_parameter(Ocsigen_request.get_params_flatrequest_info)inOcsigen_request.update~get_params_flatrequest_info)}|None->rletget_site_dirsitedata=sitedata.site_dirletget_site_dir_stringsitedata=sitedata.site_dir_string(* Returns if we want secure cookie *)letget_secure~secure_o~sitedata()=matchsecure_owithNone->sitedata.secure_cookies|Somes->smoduleTo_and_of_shared=struct(* FIXME : work-around for weak polymorphism in create :( *)typewrappertype'at={server:'ato_and_of;client:'ato_and_ofEliom_client_value.toption;wrapper:wrapper}[@@warning"-69"]letwrapper:wrapper=Obj.magic@@Eliom_wrap.create_wrapper@@function|{client=Sometao;_}->tao|{client=None;_}->failwith"Cannot wrap user type parameter.\nUse the ?client_to_and_of parameter of Eliom_parameter.user_type\nor (Eliom_parameter.all_suffix_user)"letto_string{server={to_string;_};_}=to_stringletof_string{server={of_string;_};_}=of_stringletto_and_of{server;_}=serverletcreate?client_to_and_ofserver={server;client=client_to_and_of;wrapper}endletclient_html_file()=failwith"client_html_file is only defined on client"