123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560# 1 "src/lib/eliom_uri.shared.ml"(* Ocsigen
* http://www.ocsigen.org
* Module Eliom_uri
* 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.
*)(* Building href *)letrecstring_of_url_path'=function|[]->""|[a]whena=Eliom_common.eliom_suffix_internal_name->""|[a]->Eliom_lib.Url.encode~plus:falsea|a::b::lwhenb=Eliom_common.eliom_suffix_internal_name->string_of_url_path'(a::l)|a::lwhena=Eliom_common.eliom_suffix_internal_name->string_of_url_path'l|a::l->Eliom_lib.Url.encode~plus:falsea^"/"^string_of_url_path'lletstring_of_url_path_suffu=function|None->string_of_url_path'u|Somesuff->letpref=string_of_url_path'uinletsuf=string_of_url_path'suffinifString.lengthpref=0thensufelseString.concat"/"[pref;suf]letreconstruct_absolute_url_path=string_of_url_path_suffletreconstruct_relative_url_pathcurrent_urlu=letrecdropcururldesturl=matchcururl,desturlwith|_::l,[_]->l,desturl|[_],m->[],m|a::l,b::mwhena=b->droplm|_::l,m->l,m|[],m->[],minletrecmakedotdot=function|[]->[](* | [a] -> "" *)|_::l->".."::makedotdotlinletaremonter,aaller=dropcurrent_urluinmakedotdotaremonter@aallerletreconstruct_relative_url_path_stringcurrent_urlusuff=letrelurl=reconstruct_relative_url_pathcurrent_urluinlets=string_of_url_path_suffrelurlsuffinifString.lengths=0thenEliom_common.defaultpagenameelseifs.[0]='/'then(* possible with optional parameters *)"./"^selsesletmake_actual_path=Eliom_common.make_actual_path(*****************************************************************************)letmake_proto_prefix?hostname?porthttps:string=letsp=Eliom_common.get_sp_option()inletssl=matchspwith|Somesp->Eliom_request_info.get_csp_ssl_spsp|None->falseinlethost=matchhostname,spwith|None,Somesp->Eliom_request_info.get_csp_hostname_spsp|None,None->Eliom_config.get_default_hostname()|Someh,_->hinletport=matchport,spwith|Somep,_->p|None,Somesp->ifhttps=sslthenEliom_request_info.get_csp_server_port_spspelseifhttpsthenEliom_config.get_default_sslport()elseEliom_config.get_default_port()|None,None->ifhttpsthenEliom_config.get_default_sslport()elseEliom_config.get_default_port()inEliom_lib.Url.make_absolute_url~https~host~port"/"letis_httpshttpssslservice=https=Sometrue||(https=None&&Eliom_config.default_protocol_is_https())||Eliom_service.httpsservice||(https=None&&ssl)letmake_uri_components_?(* does not take into account getparams *)absolute?((* absolute is used to force absolute link.
The default is false for regular application.
But for client side apps (mobile apps), it is true, because
relative URLs are used for local assets. *)absolute_path=false)?(* used to force absolute link without protocol/server/port *)https(typea)~(service:(_,_,_,a,_,_,_,_,_,_,_)Eliom_service.t)?hostname?port?fragment?keep_nl_params?(nl_params=Eliom_parameter.empty_nl_params_set)()=letabsolute=matchabsolutewith|Somea->a|None->!Eliom_common.is_client_app&¬(Eliom_service.has_client_funservice)inletssl=matchEliom_common.get_sp_option()with|Somesp->Eliom_request_info.get_csp_ssl_spsp|None->falseinlethttps=is_httpshttpssslserviceinletabsolute=ifabsolute||https<>sslthenSome(make_proto_prefix?hostname?porthttps)elseifabsolute_paththenSome"/"elseNonein(*VVV We trust current protocol? *)letnl_params=Eliom_parameter.table_of_nl_params_setnl_paramsinletkeep_nl_params=matchkeep_nl_paramswith|None->Eliom_service.keep_nl_paramsservice|Someb->bin(* for preapplied non localized and not non localized: *)letpreappnlp,preapplied_params=Eliom_service.pre_applied_parametersserviceinletnlp=matchEliom_common.get_sp_option()with|None->preappnlp|Somesp->(matchkeep_nl_paramswith|`All->(* We replace current nl params by preapplied ones *)Eliom_lib.String.Table.fold(funkeyvb->Eliom_lib.String.Table.addkeyvb)preappnlp(Eliommod_parameters.inject_param_table(Eliom_request_info.get_nl_get_params_spsp))|`Persistent->(* We replace current nl params by preapplied ones *)Eliom_lib.String.Table.foldEliom_lib.String.Table.addpreappnlp(Eliommod_parameters.inject_param_table(Eliom_request_info.get_persistent_nl_get_params_spsp))|`None->preappnlp)inletnlp=(* We replace current nl params by nl_params *)Eliom_lib.String.Table.foldEliom_lib.String.Table.addnl_paramsnlpin(* remove in nlp the one present in the service parameters *)letgetparamstype=Eliom_service.get_params_typeserviceinletnlp=Eliom_parameter.remove_from_nlpnlpgetparamstypeinlethiddenparams=Eliom_lib.String.Table.fold(fun_lbeg->l@beg)nlppreapplied_paramsinmatchEliom_service.infoservicewith|Eliom_service.Attachedattser->(leturi=letsuff=NoneinifEliom_service.is_externalservicethenEliom_service.prefixattser^"/"^(* we add the "/" even if there is no prefix, because
we should do absolute links in that case *)reconstruct_absolute_url_path(Eliom_service.full_pathattser)suffelsematchabsolutewith|Someproto_prefix->proto_prefix^reconstruct_absolute_url_path(Eliom_service.full_pathattser)suff|None->letsp=Eliom_common.get_sp()inreconstruct_relative_url_path_string(Eliom_request_info.get_csp_original_full_path_spsp)(Eliom_service.full_pathattser)suffinmatchEliom_service.get_nameattserwith|Eliom_common.SAtt_no->uri,hiddenparams,fragment|Eliom_common.SAtt_anons->(uri,(Eliom_common.get_numstate_param_name,Eliommod_parameters.insert_strings)::hiddenparams,fragment)|Eliom_common.SAtt_nameds->(uri,(Eliom_common.get_state_param_name,Eliommod_parameters.insert_strings)::hiddenparams,fragment)|Eliom_common.SAtt_csrf_safecsrf_info->letsp=Eliom_common.get_sp()inlets=Eliom_service.register_delayed_get_or_na_coservice~spcsrf_infoin(uri,(Eliom_common.get_numstate_param_name,Eliommod_parameters.insert_strings)::hiddenparams,fragment)|Eliom_common.SAtt_na_anons->(uri,(Eliom_common.naservice_num,Eliommod_parameters.insert_strings)::hiddenparams,fragment)|Eliom_common.SAtt_na_nameds->(uri,(Eliom_common.naservice_name,Eliommod_parameters.insert_strings)::hiddenparams,fragment)|Eliom_common.SAtt_na_csrf_safecsrf_info->letsp=Eliom_common.get_sp()inlets=Eliom_service.register_delayed_get_or_na_coservice~spcsrf_infoin(uri,(Eliom_common.naservice_num,Eliommod_parameters.insert_strings)::hiddenparams,fragment))|Eliom_service.Nonattachednaser->letsp=Eliom_common.get_sp()inletna_name=Eliom_service.na_namenaserinletparams'=letcurrent_get_params=ifna_name=Eliom_common.SNa_void_keepthen(Eliom_request_info.get_sisp).Eliom_common.si_all_get_but_nlelseLazy.force(Eliom_request_info.get_sisp).Eliom_common.si_all_get_but_na_nlinmatchna_namewith|Eliom_common.SNa_void_keep|Eliom_common.SNa_void_dontkeep->current_get_params|Eliom_common.SNa_get'n->(Eliom_common.naservice_num,n)::current_get_params|Eliom_common.SNa_get_n->(Eliom_common.naservice_name,n)::current_get_params|Eliom_common.SNa_get_csrf_safecsrf_info->letsp=Eliom_common.get_sp()inletn=Eliom_service.register_delayed_get_or_na_coservice~spcsrf_infoin(Eliom_common.naservice_num,n)::current_get_params|_->assertfalseinletparams=Eliommod_parameters.inject_param_listparams'@hiddenparamsinletbeg=matchabsolutewith|None->reconstruct_relative_url_path_string(Eliom_request_info.get_csp_original_full_path_spsp)(Eliom_request_info.get_original_full_path_spsp)None|Someproto_prefix->proto_prefix^Eliom_request_info.get_original_full_path_string_spspinbeg,params,fragmentletmake_uri_components?absolute?absolute_path?https(typea)~(service:(_,_,_,a,_,_,_,_,_,_,_)Eliom_service.t)?hostname?port?fragment?keep_nl_params?nl_paramsgetparams=leturi,pregetparams,fragment=make_uri_components_?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params()in(* for getparams and non localized params: *)letsuff,params=Eliom_parameter.construct_params_listEliom_lib.String.Table.empty(Eliom_service.get_params_typeservice)getparams(* if nl params were already present, they will be replaced
by new values *)inleturi=matchsuffwith|None->uri|Somesuff->letsuff=string_of_url_path'suffinifuri.[String.lengthuri-1]='/'thenuri^suffelseString.concat"/"[uri;suff]inletfragment=Eliom_lib.Option.mapEliom_lib.Url.encodefragmentinuri,params@pregetparams,fragmentletmake_string_uri_from_components(uri,params,fragment)=lets=Eliom_lib.String.may_concaturi~sep:"?"(Eliom_parameter.construct_params_stringparams)inmatchfragmentwithNone->s|Somef->Eliom_lib.String.concat"#"[s;f]letmake_string_uri?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_paramsgetparams:string=make_string_uri_from_components(make_uri_components?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_paramsgetparams)letmake_string_uri_=make_string_uriletmake_post_uri_components_?((* do not take into account postparams *)absolute=!Eliom_common.is_client_app)?(absolute_path=false)?https(typea)~(service:(_,_,_,a,_,_,_,_,_,_,_)Eliom_service.t)?hostname?port?fragment?(keep_nl_params:[`All|`Persistent|`None]option)?(nl_params=Eliom_parameter.empty_nl_params_set)?keep_get_na_paramsgetparams()=matchEliom_service.infoservicewith|Eliom_service.Attachedattser->let(uri,getparams,fragment),getname=letgetname=Eliom_service.get_nameattserinmatchgetnamewith|Eliom_common.SAtt_csrf_safecsrf_info->(* special case for post-coservices on get csrf safe services:
we must register the get service first *)letsp=Eliom_common.get_sp()inlets=Eliom_common.SAtt_anon(Eliom_service.register_delayed_get_or_na_coservice~spcsrf_info)in(make_uri_components~absolute~absolute_path?https~service:(Eliom_service.change_get_numserviceattsers)?hostname?port?fragment?keep_nl_params~nl_paramsgetparams,s)|_->(make_uri_components~absolute~absolute_path?https~service?hostname?port?fragment?keep_nl_params~nl_paramsgetparams,getname)inletpostparams=matchEliom_service.post_nameattserwith|Eliom_common.SAtt_no->[]|Eliom_common.SAtt_anons->[Eliom_common.post_numstate_param_name,s]|Eliom_common.SAtt_nameds->[Eliom_common.post_state_param_name,s]|Eliom_common.SAtt_csrf_safecsrf_info->letsp=Eliom_common.get_sp()inlets=Eliom_service.register_delayed_post_coservice~spcsrf_infogetnamein[Eliom_common.post_numstate_param_name,s]|Eliom_common.SAtt_na_anons->[Eliom_common.naservice_num,s]|Eliom_common.SAtt_na_nameds->[Eliom_common.naservice_name,s]|Eliom_common.SAtt_na_csrf_safecsrf_info->letsp=Eliom_common.get_sp()inlets=Eliom_service.register_delayed_post_coservice~spcsrf_infogetnamein[Eliom_common.naservice_num,s]inuri,getparams,fragment,Eliommod_parameters.inject_param_listpostparams|Eliom_service.Nonattachednaser->letsp=Eliom_common.get_sp()inletnl_params=Eliom_parameter.table_of_nl_params_setnl_paramsinletkeep_nl_params=matchkeep_nl_paramswith|None->Eliom_service.keep_nl_paramsservice|Someb->binletpreappnlp,preapp=Eliom_service.pre_applied_parametersserviceinletnlp=matchkeep_nl_paramswith|`All->(* We replace current nl params by preapplied ones *)Eliom_lib.String.Table.foldEliom_lib.String.Table.addpreappnlp(Eliommod_parameters.inject_param_table(Eliom_request_info.get_nl_get_params()))|`Persistent->(* We replace current nl params by preapplied ones *)Eliom_lib.String.Table.foldEliom_lib.String.Table.addpreappnlp(Eliommod_parameters.inject_param_table(Eliom_request_info.get_persistent_nl_get_params_spsp))|`None->preappnlpinletnlp=(* We replace current nl params by nl_params *)Eliom_lib.String.Table.foldEliom_lib.String.Table.addnl_paramsnlpin(* for getparams and non localized params: *)let_suff,params=Eliom_parameter.construct_params_listnlp(Eliom_service.get_params_typeservice)getparams(* if nl params were already present, they will be replaced
by new values *)(* getparams can be something else than []
if we have added nl params to the service (?) *)inletparams=params@preappinletkeep_get_na_params=matchkeep_get_na_paramswith|Someb->b|None->Eliom_service.na_keep_get_na_paramsnaserinletparams=params@Eliommod_parameters.inject_param_list(ifkeep_get_na_paramsthen(Eliom_request_info.get_sisp).Eliom_common.si_all_get_but_nlelseLazy.force(Eliom_request_info.get_sisp).Eliom_common.si_all_get_but_na_nl)inletssl=Eliom_request_info.get_csp_ssl_spspinlethttps=is_httpshttpssslserviceinletabsolute=ifabsolute||https<>sslthenSome(make_proto_prefix?hostname?porthttps)elseifabsolute_paththenSome"/"elseNonein(* absolute URL does not work behind a reverse proxy! *)leturi=matchabsolutewith|Someproto_prefix->if!Eliom_common.is_client_app&&lets=Eliom_request_info.get_original_full_path_string_spspands'=Eliom_common.client_html_file()inletn=String.lengthsandn'=String.lengths'inn>=n'&&String.(subs(n-n')n')=s'then(* Workaround for GitHub issue #465.
Given an app under a certain path and a server function,
we would perform requests on
http://${SERVER}/${LOCAL_PATH},
where ${LOCAL_PATH} refers to the file system on the
mobile device. This is both wrong (because it doesn't
take care of the application path) and a security issue.
To fix the issue, if the URL contains
[Eliom_common.client_html_file ()] (default:
"eliom.html"), we disregard it and use the site dir as
the path. *)letsd=Eliom_request_info.get_site_dir()inproto_prefix^String.concat"/"sd^"/"elseproto_prefix^Eliom_request_info.get_original_full_path_string_spsp|None->reconstruct_relative_url_path_string(Eliom_request_info.get_csp_original_full_path_spsp)(Eliom_request_info.get_original_full_path_spsp)Noneinletnaservice_line=matchEliom_service.na_namenaserwith|Eliom_common.SNa_post'n->Eliom_common.naservice_num,n|Eliom_common.SNa_post_n->Eliom_common.naservice_name,n|Eliom_common.SNa_post_csrf_safecsrf_info->letn=Eliom_service.register_delayed_get_or_na_coservice~spcsrf_infoinEliom_common.naservice_num,n|_->assertfalseinletfragment=None(* fragment is not sent to the server *)inletpostparams=[naservice_line]inuri,params,fragment,Eliommod_parameters.inject_param_listpostparamsletmake_post_uri_components?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_paramsgetparamspostparams=leturi,getparams,fragment,prepostparams=make_post_uri_components_?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_paramsgetparams()inlet_,postparams=Eliom_parameter.construct_params_listEliom_lib.String.Table.empty(Eliom_service.post_params_typeservice)postparamsinuri,getparams,fragment,postparams@prepostparamsletmake_post_uri_components__=make_post_uri_components(**** Tab cookies: *)(*VVV
WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
We do not take into account the suffix for computing process cookies
of GET forms (because the suffix is taken from the form).
This corresponds to what the browser is doing with session cookies.
For links and POST forms, the url already contains the suffix.
It is taken into account for computing process cookies.
Again, it is what the browser is doing for session cookies.
This is not completely satisfactory,
but should always do what we want,
but for very non-standard uses of cookies ...
*)letmake_cookies_info(https,service)=(* https is what the user asked while creating the link/form *)letget_path_(typea)~(* simplified version of make_uri_components.
Returns only the absolute path without
protocol/server/port AND WITHOUT SUFFIX *)(service:(_,_,_,a,_,_,_,_,_,_,_)Eliom_service.t)=matchEliom_service.infoservicewith|Eliom_service.Attachedattser->ifEliom_service.is_externalservicethenNoneelseSome(Eliom_service.full_pathattser)|Eliom_service.Nonattached_naser->Some(Eliom_request_info.get_csp_original_full_path())inmatchget_path_~servicewith|None->None|Somepath->letssl=Eliom_request_info.get_csp_ssl()inlethttps=https=Sometrue||Eliom_service.httpsservice||(https=None&&ssl)inSome(https,path)