From 72382c82ccfe327da273b8038c08b66d88a9fe2d Mon Sep 17 00:00:00 2001 From: Vasilis Papavasileiou Date: Tue, 13 Feb 2018 11:39:46 +0100 Subject: [PATCH] Fuse Ocsigen_local_files into staticmod --- doc/indexdoc | 1 - src/Makefile.filelist | 1 - src/extensions/.depend | 12 +- src/extensions/staticmod.ml | 217 ++++++++++++++++++++++++++- src/server/.depend | 5 - src/server/Makefile | 3 +- src/server/ocsigen_local_files.ml | 231 ----------------------------- src/server/ocsigen_local_files.mli | 69 --------- 8 files changed, 217 insertions(+), 322 deletions(-) delete mode 100644 src/server/ocsigen_local_files.ml delete mode 100644 src/server/ocsigen_local_files.mli diff --git a/doc/indexdoc b/doc/indexdoc index 08519b160..fdefe6f54 100644 --- a/doc/indexdoc +++ b/doc/indexdoc @@ -14,7 +14,6 @@ Ocsigen_config {2 Extending Ocsigen Server} {!modules: Ocsigen_extensions -Ocsigen_local_files Ocsigen_headers Ocsigen_stream Ocsigen_comet diff --git a/src/Makefile.filelist b/src/Makefile.filelist index e6278b521..61420bc3e 100644 --- a/src/Makefile.filelist +++ b/src/Makefile.filelist @@ -19,7 +19,6 @@ INTF_BASE := baselib/ocsigen_cache.cmi \ server/ocsigen_multipart.cmi \ server/ocsigen_extensions.cmi \ server/ocsigen_parseconfig.cmi \ - server/ocsigen_local_files.cmi \ server/ocsigen_server.cmi INTF := ${INTF_BASE} diff --git a/src/extensions/.depend b/src/extensions/.depend index 74db47376..a418c4ee6 100644 --- a/src/extensions/.depend +++ b/src/extensions/.depend @@ -72,13 +72,13 @@ rewritemod.cmo : ../server/ocsigen_request.cmi ../baselib/ocsigen_lib.cmi \ rewritemod.cmx : ../server/ocsigen_request.cmx ../baselib/ocsigen_lib.cmx \ ../server/ocsigen_extensions.cmx ../http/ocsigen_cookie_map.cmx staticmod.cmo : ../server/ocsigen_server.cmi ../server/ocsigen_response.cmi \ - ../server/ocsigen_request.cmi ../server/ocsigen_local_files.cmi \ - ../baselib/ocsigen_lib.cmi ../http/ocsigen_header.cmi \ - ../server/ocsigen_extensions.cmi staticmod.cmi + ../server/ocsigen_request.cmi ../baselib/ocsigen_lib.cmi \ + ../http/ocsigen_header.cmi ../server/ocsigen_extensions.cmi \ + ../server/ocsigen_config.cmi staticmod.cmi staticmod.cmx : ../server/ocsigen_server.cmx ../server/ocsigen_response.cmx \ - ../server/ocsigen_request.cmx ../server/ocsigen_local_files.cmx \ - ../baselib/ocsigen_lib.cmx ../http/ocsigen_header.cmx \ - ../server/ocsigen_extensions.cmx staticmod.cmi + ../server/ocsigen_request.cmx ../baselib/ocsigen_lib.cmx \ + ../http/ocsigen_header.cmx ../server/ocsigen_extensions.cmx \ + ../server/ocsigen_config.cmx staticmod.cmi staticmod.cmi : ../server/ocsigen_server.cmi \ ../server/ocsigen_extensions.cmi userconf.cmo : ../server/ocsigen_request.cmi ../baselib/ocsigen_lib.cmi \ diff --git a/src/extensions/staticmod.ml b/src/extensions/staticmod.ml index 5b3cced72..bc15c85a1 100644 --- a/src/extensions/staticmod.ml +++ b/src/extensions/staticmod.ml @@ -24,6 +24,210 @@ let name = "staticmod" let section = Lwt_log.Section.make "ocsigen:ext:staticmod" +exception Fail_403 +exception Fail_404 +exception Not_readable_directory + +(* Policies for following symlinks *) +type symlink_policy = + stat:Unix.LargeFile.stats -> lstat:Unix.LargeFile.stats -> bool + +let never_follow_symlinks : symlink_policy = + fun ~stat ~lstat -> false + +let follow_symlinks_if_owner_match : symlink_policy = + fun ~stat ~lstat -> + stat.Unix.LargeFile.st_uid = lstat.Unix.LargeFile.st_uid + +(* checks that [filename] can be followed depending on the predicate + [policy] which must receives as argument both the results + of calling [stat] and [lstat] on filenam. + If supplied, [stat] must be the result of calling [Unix.stat] on + [filename] *) +let check_symlinks_aux + filename ?(stat=Unix.LargeFile.stat filename) (policy : symlink_policy) = + let lstat = Unix.LargeFile.lstat filename in + if lstat.Unix.LargeFile.st_kind = Unix.S_LNK then + policy ~stat ~lstat + else + true + +(* Check that there are no invalid symlinks in the directories leading to + [filename]. Paths upwards [no_check_for] are not checked. *) +let rec check_symlinks_parent_directories + ~filename ~no_check_for (policy : symlink_policy) = + if filename = "/" || filename = "." || Some filename = no_check_for then + true + else + let dirname = Filename.dirname filename in + check_symlinks_aux dirname policy && + check_symlinks_parent_directories ~filename:dirname ~no_check_for policy + +(* Check that [filename] can be reached according to the given + symlink policy *) +let check_symlinks ~no_check_for ~filename policy = + let aux policy = + if filename = "/" then + (* The root cannot be a symlink, and this avoids some degenerate + cases later on *) + true + else + let filename = + (* [filename] should start by at least a slash, as + [Filename.is_relative filename] should be false. Hence the length + should be at least 1 *) + (* We remove an eventual trailing slash, in order to avoid a + needless recursion in check_symlinks_parent_directories, and so + that Unix.lstat returns the correct result (Unix.lstat "foo/" and + Unix.lstat "foo" return two different results...) *) + let len = String.length filename - 1 in + if filename.[len] = '/' then + String.sub filename 0 len + else + filename + in + check_symlinks_aux filename policy && + check_symlinks_parent_directories filename no_check_for policy + in + match policy with + | `Always -> + true + | `No -> + aux never_follow_symlinks + | `Owner_match -> + aux follow_symlinks_if_owner_match + +let check_dotdot = + let regexp = Ocsigen_lib.Netstring_pcre.regexp "(/\\.\\./)|(/\\.\\.$)" in + fun ~filename -> + (* We always reject .. in filenames. In URLs, .. have already + been removed by the server, but the filename may come from + somewhere else than URLs ... *) + try + ignore + (Ocsigen_lib.Netstring_pcre.search_forward regexp filename 0); + false + with Not_found -> true + +let can_send filename request = + let filename = + Ocsigen_lib.Url.split_path filename + |> Ocsigen_lib.Url.norm_path + |> Ocsigen_lib.Url.join_path + in + Lwt_log.ign_info_f ~section "checking if file %s can be sent" filename; + let matches arg = + Ocsigen_lib.Netstring_pcre.string_match + (Ocsigen_extensions.do_not_serve_to_regexp arg) + filename 0 <> + None + in + if matches request.Ocsigen_extensions.do_not_serve_403 then ( + Lwt_log.ign_info ~section "this file is forbidden"; + raise Fail_403) + else + if matches request.Ocsigen_extensions.do_not_serve_404 then ( + Lwt_log.ign_info ~section "this file must be hidden"; + raise Fail_404) + +(* given [filename], we search for it in the local filesystem and + - we return ["filename/index.html"] if [filename] corresponds to + a directory, ["filename/index.html"] is valid, and ["index.html"] + is one possible index (trying all possible indexes in order) + - we raise [Fail_404] if [filename] corresponds to a directory, + no index exists and [list_dir_content] is false. + Warning: this behaviour is not the same as Apache's but it corresponds + to a missing service in Eliom (answers 404). This also allows to have + an Eliom service after a "forbidden" directory + - we raise [Fail_403] if [filename] is a symlink that must + not be followed + - raises [Fail_404] if [filename] does not exist, or is a special file + - otherwise returns [filename] +*) +(* See also module Files in eliom.ml *) +let resolve + ?no_check_for + ~request:({Ocsigen_extensions.request_config} as request) + ~filename () = + (* We only accept absolute filenames in daemon mode, + as we do not really know what is the current directory *) + let filename = + if Filename.is_relative filename && Ocsigen_config.get_daemon () then + "/"^filename + else + filename + in + try + Lwt_log.ign_info_f ~section "Testing \"%s\"." filename; + let stat = Unix.LargeFile.stat filename in + let (filename, stat) = + if stat.Unix.LargeFile.st_kind = Unix.S_DIR then + if filename.[String.length filename - 1] <> '/' then begin + (* In this case, [filename] is a directory but this is not visible in + its name as there is no final slash. We signal this fact to + Ocsigen, which will then issue a 301 redirection to "filename/" *) + Lwt_log.ign_info_f ~section "LocalFiles: %s is a directory" filename; + raise + (Ocsigen_extensions.Ocsigen_is_dir + (Ocsigen_extensions.new_url_of_directory_request request)) + end + + else + let rec find_index = function + | [] -> + (* No suitable index, we try to list the directory *) + if request_config.Ocsigen_extensions.list_directory_content then ( + Lwt_log.ign_info ~section "Displaying directory content"; + (filename, stat) + ) else ( + (* No suitable index *) + Lwt_log.ign_info ~section "No index and no listing"; + raise Not_readable_directory) + | e :: q -> + let index = filename ^ e in + Lwt_log.ign_info_f ~section "Testing \"%s\" as possible index." index; + try + (index, Unix.LargeFile.stat index) + with + | Unix.Unix_error (Unix.ENOENT, _, _) -> find_index q + in + find_index + request_config.Ocsigen_extensions.default_directory_index + + else (filename, stat) + in + if not (check_dotdot ~filename) + then + (Lwt_log.ign_info_f ~section "Filenames cannot contain .. as in \"%s\"." filename; + raise Fail_403) + else if check_symlinks ~filename ~no_check_for + request_config.Ocsigen_extensions.follow_symlinks + then ( + can_send filename request_config; + (* If the previous function did not fail, we are authorized to + send this file *) + Lwt_log.ign_info_f ~section "Returning \"%s\"." filename; + if stat.Unix.LargeFile.st_kind = Unix.S_REG then + (* The string argument represents the real file/directory to + serve, eg. foo/index.html instead of foo *) + `File filename + else if stat.Unix.LargeFile.st_kind = Unix.S_DIR then + `Dir filename + else + raise Fail_404 + ) + else ( + (* [filename] is accessed through as symlink which we should not + follow according to the current policy *) + Lwt_log.ign_info_f ~section "Failed symlink check for \"%s\"." filename; + raise Fail_403) + with + (* We can get an EACCESS here, if are missing some rights on a directory *) + | Unix.Unix_error (Unix.EACCES,_,_) -> + raise Fail_403 + | Unix.Unix_error (Unix.ENOENT,_,_) -> + raise Fail_404 + exception Not_concerned (* Structures describing the static pages a each virtual server *) @@ -108,8 +312,7 @@ let find_static_page ~request ~usermode ~dir ~(err : Cohttp.Code.status) ~pathst | _ -> raise Not_concerned in if usermode = None || correct_user_local_file filename then - (status_filter, - Ocsigen_local_files.resolve ?no_check_for:root ~request ~filename ()) + status_filter, resolve ?no_check_for:root ~request ~filename () else raise (Ocsigen_extensions.Error_in_user_config_file "Staticmod: cannot use '..' in user paths") @@ -131,9 +334,9 @@ let gen ~usermode ?cache dir = function in let fname = match page with - | Ocsigen_local_files.RFile fname -> + | `File fname -> fname - | Ocsigen_local_files.RDir _ -> + | `Dir _ -> failwith "FIXME: staticmod dirs not implemented" in Cohttp_lwt_unix.Server.respond_file ~fname () >>= fun answer -> @@ -164,15 +367,15 @@ let gen ~usermode ?cache dir = function in Lwt.return (Ocsigen_extensions.Ext_found (fun () -> Lwt.return answer)) and catch_block = function - | Ocsigen_local_files.Failed_403 -> + | Fail_403 -> Lwt.return (Ocsigen_extensions.Ext_next `Forbidden) (* XXX We should try to leave an information about this error for later *) - | Ocsigen_local_files.NotReadableDirectory -> + | Not_readable_directory -> Lwt.return (Ocsigen_extensions.Ext_next err) | Ocsigen_extensions.NoSuchUser | Ocsigen_extensions.Not_concerned - | Ocsigen_local_files.Failed_404 -> + | Fail_404 -> Lwt.return (Ocsigen_extensions.Ext_next err) | e -> Lwt.fail e diff --git a/src/server/.depend b/src/server/.depend index 8576287c8..216134daf 100644 --- a/src/server/.depend +++ b/src/server/.depend @@ -26,11 +26,6 @@ ocsigen_extensions.cmi : ocsigen_response.cmi ocsigen_request.cmi \ ocsigen_multipart.cmi ../baselib/ocsigen_lib.cmi \ ../http/ocsigen_cookie_map.cmi ocsigen_command.cmi \ ../http/ocsigen_charset_mime.cmi -ocsigen_local_files.cmo : ../baselib/ocsigen_lib.cmi ocsigen_extensions.cmi \ - ocsigen_config.cmi ocsigen_local_files.cmi -ocsigen_local_files.cmx : ../baselib/ocsigen_lib.cmx ocsigen_extensions.cmx \ - ocsigen_config.cmx ocsigen_local_files.cmi -ocsigen_local_files.cmi : ocsigen_extensions.cmi ocsigen_messages.cmo : ocsigen_config.cmi ocsigen_messages.cmi ocsigen_messages.cmx : ocsigen_config.cmx ocsigen_messages.cmi ocsigen_messages.cmi : diff --git a/src/server/Makefile b/src/server/Makefile index a97a65e40..f39ed51a7 100644 --- a/src/server/Makefile +++ b/src/server/Makefile @@ -16,8 +16,7 @@ all: byte opt FILES := ocsigen_config.ml ocsigen_messages.ml ocsigen_command.ml \ ocsigen_multipart.ml ocsigen_request.ml ocsigen_response.ml \ ocsigen_cohttp.ml ocsigen_extensions.ml \ - ocsigen_parseconfig.ml ocsigen_local_files.ml \ - ocsigen_server.ml + ocsigen_parseconfig.ml ocsigen_server.ml byte:: ${PROJECTNAME}.cma opt:: ${PROJECTNAME}.cmxa diff --git a/src/server/ocsigen_local_files.ml b/src/server/ocsigen_local_files.ml deleted file mode 100644 index f7ef54e16..000000000 --- a/src/server/ocsigen_local_files.ml +++ /dev/null @@ -1,231 +0,0 @@ -(* Ocsigen - * http://www.ocsigen.org - * Copyright (C) 2009 Boris Yakobowski - * - * 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. -*) - -(* Display of a local file or directory. Currently used in staticmod - and eliom_predefmod *) - -let section = Lwt_log.Section.make "ocsigen:local-file" -exception Failed_403 -exception Failed_404 -exception NotReadableDirectory - -(* Policies for following symlinks *) -type symlink_policy = - stat:Unix.LargeFile.stats -> lstat:Unix.LargeFile.stats -> bool - -let never_follow_symlinks : symlink_policy = - fun ~stat ~lstat -> false - -let follow_symlinks_if_owner_match : symlink_policy = - fun ~stat ~lstat -> - stat.Unix.LargeFile.st_uid = lstat.Unix.LargeFile.st_uid - - -(* checks that [filename] can be followed depending on the predicate - [policy] which must receives as argument both the results - of calling [stat] and [lstat] on filenam. - If supplied, [stat] must be the result of calling [Unix.stat] on - [filename] *) -let check_symlinks_aux - filename ?(stat=Unix.LargeFile.stat filename) (policy : symlink_policy) = - let lstat = Unix.LargeFile.lstat filename in - if lstat.Unix.LargeFile.st_kind = Unix.S_LNK then - policy ~stat ~lstat - else - true - -(* Check that there are no invalid symlinks in the directories leading to - [filename]. Paths upwards [no_check_for] are not checked. *) -let rec check_symlinks_parent_directories ~filename ~no_check_for (policy : symlink_policy) = - if filename = "/" || filename = "." || Some filename = no_check_for then - true - else - let dirname = Filename.dirname filename in - check_symlinks_aux dirname policy && - check_symlinks_parent_directories ~filename:dirname ~no_check_for policy - - -(* Check that [filename] can be reached according to the given - symlink policy *) -let check_symlinks ~no_check_for ~filename policy = - let aux policy = - if filename = "/" then - (* The root cannot be a symlink, and this avoids some degenerate - cases later on *) - true - else - let filename = - (* [filename] should start by at least a slash, as - [Filename.is_relative filename] should be false. Hence the length - should be at least 1 *) - (* We remove an eventual trailing slash, in order to avoid a - needless recursion in check_symlinks_parent_directories, and so - that Unix.lstat returns the correct result (Unix.lstat "foo/" and - Unix.lstat "foo" return two different results...) *) - let len = String.length filename - 1 in - if filename.[len] = '/' then - String.sub filename 0 len - else - filename - in - check_symlinks_aux filename policy && - check_symlinks_parent_directories filename no_check_for policy - in - match policy with - | `Always -> - true - | `No -> - aux never_follow_symlinks - | `Owner_match -> - aux follow_symlinks_if_owner_match - -let check_dotdot = - let regexp = Ocsigen_lib.Netstring_pcre.regexp "(/\\.\\./)|(/\\.\\.$)" in - fun ~filename -> - (* We always reject .. in filenames. - In URLs, .. have already been removed by the server, - but the filename may come from somewhere else than URLs ... *) - try - ignore - (Ocsigen_lib.Netstring_pcre.search_forward regexp filename 0); - false - with Not_found -> true - -let can_send filename request = - let filename = - Ocsigen_lib.Url.split_path filename - |> Ocsigen_lib.Url.norm_path - |> Ocsigen_lib.Url.join_path - in - Lwt_log.ign_info_f ~section "checking if file %s can be sent" filename; - let matches arg = - Ocsigen_lib.Netstring_pcre.string_match - (Ocsigen_extensions.do_not_serve_to_regexp arg) - filename 0 <> - None - in - if matches request.Ocsigen_extensions.do_not_serve_403 then ( - Lwt_log.ign_info ~section "this file is forbidden"; - raise Failed_403) - else - if matches request.Ocsigen_extensions.do_not_serve_404 then ( - Lwt_log.ign_info ~section "this file must be hidden"; - raise Failed_404) - - -(* Return type of a request for a local file. The string argument - represents the real file/directory to serve, eg. foo/index.html - instead of foo *) -type resolved = - | RFile of string - | RDir of string - - -(* given [filename], we search for it in the local filesystem and - - we return ["filename/index.html"] if [filename] corresponds to - a directory, ["filename/index.html"] is valid, and ["index.html"] - is one possible index (trying all possible indexes in order) - - we raise [Failed_404] if [filename] corresponds to a directory, - no index exists and [list_dir_content] is false. - Warning: this behaviour is not the same as Apache's but it corresponds - to a missing service in Eliom (answers 404). This also allows to have - an Eliom service after a "forbidden" directory - - we raise [Failed_403] if [filename] is a symlink that must - not be followed - - raises [Failed_404] if [filename] does not exist, or is a special file - - otherwise returns [filename] -*) -(* See also module Files in eliom.ml *) -let resolve - ?no_check_for - ~request:({Ocsigen_extensions.request_config} as request) - ~filename () = - (* We only accept absolute filenames in daemon mode, - as we do not really know what is the current directory *) - let filename = - if Filename.is_relative filename && Ocsigen_config.get_daemon () then - "/"^filename - else - filename - in - try - Lwt_log.ign_info_f ~section "Testing \"%s\"." filename; - let stat = Unix.LargeFile.stat filename in - let (filename, stat) = - if stat.Unix.LargeFile.st_kind = Unix.S_DIR then - if filename.[String.length filename - 1] <> '/' then begin - (* In this case, [filename] is a directory but this is not visible in - its name as there is no final slash. We signal this fact to - Ocsigen, which will then issue a 301 redirection to "filename/" *) - Lwt_log.ign_info_f ~section "LocalFiles: %s is a directory" filename; - raise - (Ocsigen_extensions.Ocsigen_is_dir - (Ocsigen_extensions.new_url_of_directory_request request)) - end - - else - let rec find_index = function - | [] -> - (* No suitable index, we try to list the directory *) - if request_config.Ocsigen_extensions.list_directory_content then ( - Lwt_log.ign_info ~section "Displaying directory content"; - (filename, stat) - ) else ( - (* No suitable index *) - Lwt_log.ign_info ~section "No index and no listing"; - raise NotReadableDirectory) - | e :: q -> - let index = filename ^ e in - Lwt_log.ign_info_f ~section "Testing \"%s\" as possible index." index; - try - (index, Unix.LargeFile.stat index) - with - | Unix.Unix_error (Unix.ENOENT, _, _) -> find_index q - in - find_index - request_config.Ocsigen_extensions.default_directory_index - - else (filename, stat) - in - if not (check_dotdot ~filename) - then - (Lwt_log.ign_info_f ~section "Filenames cannot contain .. as in \"%s\"." filename; - raise Failed_403) - else if check_symlinks ~filename ~no_check_for - request_config.Ocsigen_extensions.follow_symlinks - then ( - can_send filename request_config; - (* If the previous function did not fail, we are authorized to - send this file *) - Lwt_log.ign_info_f ~section "Returning \"%s\"." filename; - if stat.Unix.LargeFile.st_kind = Unix.S_REG then - RFile filename - else if stat.Unix.LargeFile.st_kind = Unix.S_DIR then - RDir filename - else raise Failed_404 - ) - else ( - (* [filename] is accessed through as symlink which we should not - follow according to the current policy *) - Lwt_log.ign_info_f ~section "Failed symlink check for \"%s\"." filename; - raise Failed_403) - with - (* We can get an EACCESS here, if are missing some rights on a directory *) - | Unix.Unix_error (Unix.EACCES,_,_) -> raise Failed_403 - | Unix.Unix_error (Unix.ENOENT,_,_) -> raise Failed_404 diff --git a/src/server/ocsigen_local_files.mli b/src/server/ocsigen_local_files.mli deleted file mode 100644 index 8e083357b..000000000 --- a/src/server/ocsigen_local_files.mli +++ /dev/null @@ -1,69 +0,0 @@ -(* Ocsigen - * http://www.ocsigen.org - * Copyright (C) 2009 Boris Yakobowski - * - * 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. -*) -(** The requested file does not exists *) -exception Failed_404 -(** The requested file cannot be served: does not exists, not - enough permissions ... *) -exception Failed_403 -(** The file is a directory which we should not display *) -exception NotReadableDirectory - - - -(* -(** Default options: - - never follow symlinks - - use "index.html" as default index - - do not list the content of directories -*) -val default_options : options -*) - - -(** Local file corresponding to a request. The string argument - represents the real file or directory to serve, eg. foo/index.html - instead of foo *) -type resolved = - | RFile of string - | RDir of string - -(** Finds [filename] in the filesystem, with a possible redirection - if it is a directory. Takes into account the fact that [filename] - does not exists, is a symlink or is a directory, and raises - Failed_404 or Failed_403 accordingly. - - - we return ["filename/index.html"] if [filename] corresponds to - a directory, ["filename/index.html"] is valid, and ["index.html"] - is one possible index (trying all possible indexes in order) - - we raise [Failed_404] if [filename] corresponds to a directory, - no index exists and [list_dir_content] is false. - Warning: this behaviour is not the same as Apache's but it corresponds - to a missing service in Eliom (answers 404). This also allows to have - an Eliom service after a "forbidden" directory - - we raise [Failed_403] if [filename] is a symlink that must - not be followed - - raises [Failed_404] if [filename] does not exist, or is a special file - - otherwise returns [filename] - - [no_check_for] is supposed to be a prefix of [filename] ; - directories above [no_check_for] are not checked for symlinks *) -val resolve : - ?no_check_for:string -> - request:Ocsigen_extensions.request -> - filename:string -> unit -> resolved