Skip to content

Commit 9a70caf

Browse files
committed
Add support for directory listings in Staticmod
Add a simple generator for directory listings: - the listings are unstyled - the generation must load the full directory (so that the entries can be sorted) - consequently the generation should probably not be enabled when there are huge directories (which are usually a bad idea anyhow)
1 parent 7950e0a commit 9a70caf

File tree

1 file changed

+85
-11
lines changed

1 file changed

+85
-11
lines changed

src/extensions/staticmod.ml

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -116,27 +116,101 @@ let find_static_page ~request ~usermode ~dir ~(err : Cohttp.Code.status)
116116
(Ocsigen_extensions.Error_in_user_config_file
117117
"Staticmod: cannot use '..' in user paths")
118118

119+
(* Borrowed from TyXML:lib/xml_print.ml (and wrapped) to avoid the dependency *)
120+
let html_of_string s =
121+
let is_control c =
122+
let cc = Char.code c in
123+
cc <= 8 || cc = 11 || cc = 12 || (14 <= cc && cc <= 31) || cc = 127
124+
in
125+
let add_unsafe_char b = function
126+
| '<' -> Buffer.add_string b "&lt;"
127+
| '>' -> Buffer.add_string b "&gt;"
128+
| '"' -> Buffer.add_string b "&quot;"
129+
| '&' -> Buffer.add_string b "&amp;"
130+
| c when is_control c ->
131+
Buffer.add_string b "&#";
132+
Buffer.add_string b (string_of_int (Char.code c));
133+
Buffer.add_string b ";"
134+
| c -> Buffer.add_char b c
135+
in
136+
let encode_unsafe_char s =
137+
let b = Buffer.create (String.length s) in
138+
String.iter (add_unsafe_char b) s;
139+
Buffer.contents b
140+
in
141+
encode_unsafe_char s
142+
(* End of borrowed code *)
143+
144+
let respond_dir relpath dname : (Cohttp.Response.t * Cohttp_lwt.Body.t) Lwt.t =
145+
let readsortdir =
146+
(* Read a complete directory and sort its entries *)
147+
let chunk_size = 1024 in
148+
let rec aux entries dir =
149+
Lwt_unix.readdir_n dir chunk_size >>= fun chunk ->
150+
let entries = chunk :: entries in
151+
if Array.length chunk < chunk_size
152+
then Lwt.return entries
153+
else aux entries dir
154+
in
155+
Lwt_unix.opendir dname >>= fun dir ->
156+
Lwt.finalize
157+
(fun () ->
158+
aux [] dir >|= fun entries ->
159+
List.sort compare (List.concat_map Array.to_list entries))
160+
(fun () -> Lwt_unix.closedir dir)
161+
in
162+
Lwt.catch
163+
(fun () ->
164+
readsortdir >>= fun entries ->
165+
let title = html_of_string ("Directory listing for /" ^ relpath) in
166+
let entries =
167+
List.filter_map
168+
(function
169+
| "." | ".." -> None
170+
| e ->
171+
Some
172+
(Printf.sprintf "<li><a href=\"%t\">%t</a></li>"
173+
(fun () -> Ocsigen_lib.Url.encode ~plus:false e)
174+
(fun () -> html_of_string e)))
175+
entries
176+
in
177+
(* Chunks of [html (head (title x) []) (body [h1 [x]; ul y])] *)
178+
let chunk1 =
179+
{|<!DOCTYPE html>
180+
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>|}
181+
and chunk2 = {|</title></head><body><h1>|}
182+
and chunk3 = {|</h1><ul>|}
183+
and chunkend = {|</ul></body></html>|} in
184+
let doc =
185+
chunk1 :: title :: chunk2 :: title :: chunk3 :: (entries @ [chunkend])
186+
in
187+
let headers = Cohttp.Header.init_with "content-type" "text/html" in
188+
Lwt.return
189+
( Cohttp.Response.make ~status:`OK ~headers ()
190+
, Cohttp_lwt.Body.of_string_list doc ))
191+
(function
192+
| Unix.Unix_error _ -> Cohttp_lwt_unix.Server.respond_not_found ()
193+
| exn -> Lwt.fail exn)
194+
119195
let gen ~usermode ?cache dir = function
120196
| Ocsigen_extensions.Req_found _ ->
121197
Lwt.return Ocsigen_extensions.Ext_do_nothing
122198
| Ocsigen_extensions.Req_not_found
123199
(err, ({Ocsigen_extensions.request_info; _} as request)) ->
124200
let try_block () =
125201
Lwt_log.ign_info ~section "Is it a static file?";
202+
let pathstring =
203+
Ocsigen_lib.Url.string_of_url_path ~encode:false
204+
(Ocsigen_request.sub_path request_info)
205+
in
126206
let status_filter, page =
127-
let pathstring =
128-
Ocsigen_lib.Url.string_of_url_path ~encode:false
129-
(Ocsigen_request.sub_path request_info)
130-
in
131207
find_static_page ~request ~usermode ~dir ~err ~pathstring
132208
in
133-
let fname =
134-
match page with
135-
| Ocsigen_local_files.RFile fname -> fname
136-
| Ocsigen_local_files.RDir _ ->
137-
failwith "FIXME: staticmod dirs not implemented"
138-
in
139-
Cohttp_lwt_unix.Server.respond_file ~fname () >>= fun answer ->
209+
(match page with
210+
| Ocsigen_local_files.RFile fname ->
211+
Cohttp_lwt_unix.Server.respond_file ~fname ()
212+
| Ocsigen_local_files.RDir dname -> respond_dir pathstring dname)
213+
>>= fun answer ->
140214
let answer = Ocsigen_response.of_cohttp answer in
141215
let answer =
142216
if not status_filter

0 commit comments

Comments
 (0)