123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569# 1 "src/lib/eliom_mkreg.server.ml"(* Ocsigen
* http://www.ocsigen.org
* Module Eliom_mkreg
* 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.
*)moduleS=Eliom_servicelet(>>=)=Lwt.(>>=)letsuffix_redir_uri_key=Polytables.make_key()type('options,'page,'result)param={send:?options:'options->?charset:string->?code:int->?content_type:string->?headers:Ocsigen_header.t->'page->Ocsigen_response.tLwt.t;send_appl_content:S.send_appl_content(** Whether the service is capable to send application content when
required. This field is usually [Eliom_service.XNever]. This
value is recorded inside each service just after
registration. *);result_of_http_result:Ocsigen_response.t->'result}(* If it is an xmlHTTPrequest who asked for an internal application
service but the current service
does not belong to the same application,
we ask the browser to stop the program and do a redirection.
This can happen for example after an action,
when the fallback service does not belong to the application.
We can not do a regular redirection because
it is an XHR. We use our own redirections.
*)(*VVV
An alternative, to avoid the redirection with rc,
would be to answer the full page and to detect on client side
that it is not the answer of an XRH (using content-type)
and ask the browser to act as if it were a regular request.
Is it possible to do that?
Drawback: The URL will be wrong
Other solution: send the page and ask the browser to put it in the cache
during a few seconds. Then redirect. But can we trust the browser cache?
*)(* the test to know before page generation if the page can contain
application data. This test is not exhaustif: services declared as
XAlways can contain classical content, but we can't know it at this
point: we must wait for the page to be generated and then see if it
is effectively application content. *)letcheck_beforenameservice=matchS.send_appl_contentservice(* the appl name of the service *)with|S.XSame_appl(an,_)whenan=name->(* Same appl, it is ok *)false|S.XAlways->(* It is an action *)false|_->true(* This test check if there is a header set only by
Eliom_registration.App. This test is sufficient, but it is better
to stop page generation as soon as we know that the content won't
be needed: hence we test what we can before page generation. *)letcheck_afternameresult=matchOcsigen_response.headerresult(Ocsigen_header.Name.of_stringEliom_common_base.appl_name_header_name)with|Someappl_name->not(appl_name=name)|None->(* not an application content *)trueletcheck_process_redirspfparam=letredir=ifEliom_request_info.expecting_process_page()thenmatchsp.Eliom_common.sp_client_appl_namewith(* the appl name as sent by browser *)|None->false(* should not happen *)|Someanr->fanrparam(* the browser asked application eliom data
(content only) for application anr *)elsefalseinifredirthenletri=Eliom_request_info.get_ri_spspinLwt.fail(* we answer to the xhr
by asking an HTTP redirection *)(Eliom_common.Eliom_do_half_xhr_redirection("/"^Eliom_lib.String.may_concat(Ocsigen_request.original_full_path_stringri)~sep:"?"(Eliom_parameter.construct_params_string(Ocsigen_request.get_params_flatri))))(* We do not put hostname and port.
It is ok with half or full xhr redirections. *)(* If an action occurred before,
it may have removed some get params form ri *)elseLwt.return_unitletsend_with_cookiessppages?options?charset?code?content_type?headerscontent=let%lwtresult=pages.send?options?charset?code?content_type?headerscontentinlet%lwt()=check_process_redirspcheck_afterresultinlet%lwttab_cookies=Eliommod_cookies.compute_cookies_to_sendsp.Eliom_common.sp_sitedatasp.Eliom_common.sp_tab_cookie_infosp.Eliom_common.sp_user_tab_cookiesin(* TODO: do not add header when no cookies *)letresponse=letresponse,_=Ocsigen_response.to_cohttpresultinletheaders=Cohttp.Header.add(Cohttp.Response.headersresponse)Eliom_common_base.set_tab_cookies_header_name(Eliommod_cookies.cookieset_to_jsontab_cookies)in{responsewithCohttp.Response.headers}andcookies=Ocsigen_cookie_map.add_multi(Eliom_request_info.get_user_cookies())(Ocsigen_response.cookiesresult)inLwt.return(Ocsigen_response.updateresult~cookies~response)letregister_auxpages?options?charset?code?content_type?headerstable(typea)~(service:(_,_,_,a,_,_,_,_,_,_,_)S.t)?(error_handler=funl->raise(Eliom_common.Eliom_Typing_Errorl))page_generator=S.set_send_appl_contentservicepages.send_appl_content;matchS.infoservicewith|S.Attachedattser->(letkey_meth=S.which_meth_untypedserviceinletattserget=S.get_nameattserinletattserpost=S.post_nameattserinletsuffix_with_redirect=S.redirect_suffixattserinletpriority=S.priorityattserinletsgpt=S.get_params_typeserviceandsppt=S.post_params_typeserviceinlets_id=ifattserget=Eliom_common.SAtt_no||attserpost=Eliom_common.SAtt_nothenEliom_parameter.(anonymise_params_typesgpt,anonymise_params_typesppt)else0,0ands_max_use=S.max_useserviceands_expire=matchS.timeoutservicewith|None->None|Somet->Some(t,ref(t+.Unix.time()))inletftable((_attserget,_attserpost)asattsernames)=Eliom_route.add_serviceprioritytable(S.sub_pathattser){Eliom_common.key_state=attsernames;Eliom_common.key_meth:>Eliom_common.meth}{s_id;s_max_use;s_expire;s_f=(funnosuffixversionsp->Lwt.with_valueEliom_common.sp_key(Somesp)(fun()->letri=Eliom_request_info.get_ri_spspandsuff=Eliom_request_info.get_suffix_spspinLwt.catch(fun()->Eliom_parameter.reconstruct_params~spsgpt(Some(Lwt.return(Ocsigen_request.get_params_flatri)))(Some(Lwt.return[]))nosuffixversionsuff>>=fung->letpost_params=Eliom_request_info.get_post_params_spspinletfiles=Eliom_request_info.get_files_spspinEliom_parameter.reconstruct_params~spspptpost_paramsfilesfalseNone>>=funp->(* GRGR TODO: avoid
Eliom_uri.make_string_uri_. But we need to
"downcast" the type of service to the
correct "get service". *)(ifEliom_request_info.get_http_method()=`GET&&nosuffixversion&&suffix_with_redirectthen(if(* it is a suffix service in version
without suffix. We redirect. *)not(Eliom_request_info.expecting_process_page())thenletredir_uri=Eliom_uri.make_string_uri_~absolute:true~service:(service:('a,'b,_,_,_,S.non_ext,S.reg,_,'c,'d,'return)S.t:>('a,'b,_,_,_,_,_,_,'c,'d,'return)S.t)ginLwt.fail(Eliom_common.Eliom_do_redirectionredir_uri)else(* It is an internal application form.
We don't redirect but we set this
special information for url to be displayed
by the browser
(see Eliom_request_info.rebuild_uri_without_iternal_form_info_)
*)letredir_uri=Eliom_uri.make_string_uri_~serviceginletrc=Eliom_request_info.get_request_cache_spspinPolytables.set~table:rc~key:suffix_redir_uri_key~value:redir_uri;Lwt.return_unit)elseLwt.return_unit)>>=fun()->check_process_redirspcheck_beforeservice>>=fun()->page_generatorgp)(function|Eliom_common.Eliom_Typing_Errorl->error_handlerl|e->Lwt.faile)>>=funcontent->send_with_cookiessppages?options?charset?code?content_type?headerscontent))}inmatchkey_meth,attserget,attserpostwith|((`Post|`Put|`Delete),_,Eliom_common.SAtt_csrf_safe(id,scope,secure_session))->lettablereg,forsession=matchtablewith|Eliom_lib.Leftglobtbl->globtbl,false|Eliom_lib.Right(sp,ct,sec)->ifsecure_session<>sec||scope<>ctthenraiseS.Wrong_session_table_for_CSRF_safe_coservice;(!(Eliom_state.get_session_service_table?secure:secure_session~scope~sp()),true)inS.set_delayed_post_registration_functiontableregid(fun~spattserget->letn=S.new_state()inletattserpost=Eliom_common.SAtt_anonninlettable=ifforsessionthentableregelse(* we do not register in global table,
but in the table specified while creating
the csrf safe service *)!(Eliom_state.get_session_service_table?secure:secure_session~scope~sp())inftable(attserget,attserpost);n)|`Get,Eliom_common.SAtt_csrf_safe(id,scope,secure_session),_->lettablereg,forsession=matchtablewith|Leftglobtbl->globtbl,false|Right(sp,ct,sec)->ifsecure_session<>sec||ct<>scopethenraiseS.Wrong_session_table_for_CSRF_safe_coservice;(!(Eliom_state.get_session_service_table?secure:secure_session~scope~sp()),true)inS.set_delayed_get_or_na_registration_functiontableregid(fun~sp->letn=S.new_state()inletattserget=Eliom_common.SAtt_anonninlettable=ifforsessionthentableregelse(* we do not register in global table,
but in the table specified while creating
the csrf safe service *)!(Eliom_state.get_session_service_table?secure:secure_session~scope~sp())inftable(attserget,attserpost);n)|_->lettablereg=matchtablewith|Leftglobtbl->globtbl|Right(sp,scope,secure_session)->!(Eliom_state.get_session_service_table?secure:secure_session~scope~sp())inftablereg(attserget,attserpost))|S.Nonattachednaser->(letna_name=S.na_namenaserinletftablena_name=Eliom_route.add_naservicetablena_name((matchS.max_useservicewith|None->None|Somei->Some(refi)),(matchS.timeoutservicewith|None->None|Somet->Some(t,ref(t+.Unix.time()))),funsp->Lwt.with_valueEliom_common.sp_key(Somesp)(fun()->letri=Eliom_request_info.get_ri_spspinLwt.catch(fun()->Eliom_parameter.reconstruct_params~sp(S.get_params_typeservice)(Some(Lwt.return(Ocsigen_request.get_params_flatri)))(Some(Lwt.return[]))falseNone>>=fung->letpost_params=Eliom_request_info.get_post_params_spspinletfiles=Eliom_request_info.get_files_spspinEliom_parameter.reconstruct_params~sp(S.post_params_typeservice)post_paramsfilesfalseNone>>=funp->check_process_redirspcheck_beforeservice>>=fun()->page_generatorgp)(function|Eliom_common.Eliom_Typing_Errorl->error_handlerl|e->Lwt.faile)>>=funcontent->send_with_cookiessppages?options?charset?code?content_type?headerscontent))inmatchna_namewith|Eliom_common.SNa_get_csrf_safe(id,scope,secure_session)->(* CSRF safe coservice: we'll do the registration later *)lettablereg,forsession=matchtablewith|Leftglobtbl->globtbl,false|Right(sp,ct,sec)->ifsecure_session<>sec||ct<>scopethenraiseS.Wrong_session_table_for_CSRF_safe_coservice;(!(Eliom_state.get_session_service_table?secure:secure_session~scope~sp()),true)inS.set_delayed_get_or_na_registration_functiontableregid(fun~sp->letn=S.new_state()inletna_name=Eliom_common.SNa_get'ninlettable=ifforsessionthentableregelse(* we do not register in global table,
but in the table specified while creating
the csrf safe service *)!(Eliom_state.get_session_service_table?secure:secure_session~scope~sp())inftablena_name;n)|Eliom_common.SNa_post_csrf_safe(id,scope,secure_session)->(* CSRF safe coservice: we'll do the registration later *)lettablereg,forsession=matchtablewith|Leftglobtbl->globtbl,false|Right(sp,ct,sec)->ifsecure_session<>sec||ct<>scopethenraiseS.Wrong_session_table_for_CSRF_safe_coservice;(!(Eliom_state.get_session_service_table?secure:secure_session~scope~sp()),true)inS.set_delayed_get_or_na_registration_functiontableregid(fun~sp->letn=S.new_state()inletna_name=Eliom_common.SNa_post'ninlettable=ifforsessionthentableregelse(* we do not register in global table,
but in the table specified while creating
the csrf safe service *)!(Eliom_state.get_session_service_table?secure:secure_session~scope~sp())inftablena_name;n)|_->lettablereg=matchtablewith|Leftglobtbl->globtbl|Right(sp,scope,secure_session)->!(Eliom_state.get_session_service_table?secure:secure_session~scope~sp())inftableregna_name)letsendpages?options?charset?code?content_type?headerscontent=let%lwtresult=pages.send?options?charset?code?content_type?headerscontentinLwt.return(pages.result_of_http_resultresult)letregisterpages?app:_?scope?options?charset?code?content_type?headers?secure_session(typea)~(service:(_,_,_,a,_,_,S.reg,_,_,_,_)S.t)?error_handlerpage_gen=letsp=Eliom_common.get_sp_option()inmatchscope,spwith|None,None|Some`Site,None->(letauxsitedata=(matchS.infoservicewith|S.Attachedattser->Eliom_common.remove_unregisteredsitedata(S.sub_pathattser)|S.Nonattachednaser->Eliom_common.remove_unregistered_nasitedata(S.na_namenaser));register_auxpages?options?charset?code?content_type?headers(Leftsitedata.Eliom_common.global_services)~service?error_handlerpage_geninmatchEliom_common.global_register_allowed()with|Someget_current_sitedata->letsitedata=get_current_sitedata()inifsitedata.Eliom_common.site_dir<>Nonethenauxsitedataelse(* I suppose that it's a statically linked module
that is not associated with a site yet.
I will defer the registration until app is initialised. *)Ocsigen_loader.add_module_init_function(Eliom_common.get_app_name())(fun()->auxsitedata)|_->raise(Eliom_common.Eliom_site_information_not_available"register"))|None,Some_|Some`Site,Some_->register_auxpages?options?charset?code?content_type?headers?error_handler(Eliom_lib.Left(Eliom_state.get_global_table()))~servicepage_gen|_,None->raise(failwith"Missing sp while registering service")|Some(#Eliom_common.user_scopeasscope),Somesp->register_auxpages?options?charset?code?content_type?headers?error_handler(Right(sp,scope,secure_session))~servicepage_gen(* WARNING: if we create a new service without registering it,
we can have a link towards a page that does not exist!!! :-(
That's why I impose to register all service during init.
The only other way I see to avoid this is to impose a syntax extension
like "let rec" for service...
*)letcreatepages?scope?app?options?charset?code?content_type?headers?secure_session?https?name?csrf_safe?csrf_scope?csrf_secure?max_use?timeout~meth~path?error_handlerpage=letservice=S.create_unsafe?name?csrf_safe?csrf_scope:(csrf_scope:>Eliom_common.user_scopeoption)?csrf_secure?max_use?timeout?https~meth~path()inregisterpages?scope?app?options?charset?code?content_type?headers?secure_session~service?error_handlerpage;serviceletcreate_attached_getpages?scope?app?options?charset?code?content_type?headers?secure_session?https?name?csrf_safe?csrf_scope?csrf_secure?max_use?timeout~fallback~get_params?error_handlerpage=letservice=S.create_attached_get_unsafe?name?csrf_safe?csrf_scope:(csrf_scope:>Eliom_common.user_scopeoption)?csrf_secure?max_use?timeout?https~fallback~get_params()inregisterpages?scope?app?options?charset?code?content_type?headers?secure_session~service?error_handlerpage;serviceletcreate_attached_postpages?scope?app?options?charset?code?content_type?headers?secure_session?https?name?csrf_safe?csrf_scope?csrf_secure?max_use?timeout~fallback~post_params?error_handlerpage=letservice=S.create_attached_post_unsafe?name?csrf_safe?csrf_scope:(csrf_scope:>Eliom_common.user_scopeoption)?csrf_secure?max_use?timeout?https~fallback~post_params()inregisterpages?scope?app?options?charset?code?content_type?headers?secure_session~service?error_handlerpage;servicemoduleMake(Pages:Eliom_registration_sigs.PARAMwithtypeframe:=Ocsigen_response.t)=structtypepage=Pages.pagetypeoptions=Pages.optionstypereturn=Eliom_service.non_ocamltyperesult=Pages.resultletpages={send=Pages.send;send_appl_content=Pages.send_appl_content;result_of_http_result=Pages.result_of_http_result}letsend?options=sendpages?optionsletregister?app=registerpages?appletcreate?app=createpages?appletcreate_attached_get?app=create_attached_getpages?appletcreate_attached_post?app=create_attached_postpages?appendmoduleMake_poly(Pages:Eliom_registration_sigs.PARAM_POLYwithtypeframe:=Ocsigen_response.t)=structtype'apage='aPages.pagetypeoptions=Pages.optionstype'areturn='aPages.returnletpages={send=Pages.send;send_appl_content=Pages.send_appl_content;result_of_http_result=(funx->x)}letregister?app=registerpages?appletcreate?app=createpages?appletcreate_attached_get?app=create_attached_getpages?appletcreate_attached_post?app=create_attached_postpages?append