package mehari

  1. Overview
  2. Docs

Source file static.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
module type DIR = sig
  module IO : Types.IO

  type path

  val exists : path -> bool IO.t
  val kind : path -> [ `Regular_file | `Directory | `Other ] IO.t
  val read : path -> string list IO.t
  val concat : path -> string -> path
  val response_document : ?mime:Mime.t -> path -> Response.t IO.t
  val pp_io_err : Format.formatter -> exn -> unit
end

module type S = sig
  module IO : Types.IO

  type addr
  type handler = addr Handler.Make(IO).t
  type dir_path

  val static :
    ?handler:(dir_path -> handler) ->
    ?dir_listing:
      (([ `Regular_file | `Directory | `Other ] * string) list -> handler) ->
    ?index:string ->
    ?show_hidden:bool ->
    dir_path ->
    handler
end

module Make (Dir : DIR) (Addr : Types.T) :
  S
    with module IO := Dir.IO
     and type addr := Addr.t
     and type dir_path := Dir.path = struct
  type handler = Addr.t Handler.Make(Dir.IO).t

  let src = Logs.Src.create "mehari.static"

  module Log = (val Logs.src_log src)

  let ( let* ) = Dir.IO.bind

  let pp_kind fmt = function
    | `Regular_file -> Format.fprintf fmt "\u{1F4C4}"
    | `Directory -> Format.fprintf fmt "\u{1F4C1}"
    | `Other -> Format.fprintf fmt "\u{2753}"

  let default_handler path req =
    let fname = Request.param req 1 in
    let mime =
      match Mime.from_filename fname with
      | None when Filename.check_suffix fname ".gmi" -> Mime.gemini ()
      | None -> Mime.no_mime
      | Some m -> m
    in
    Dir.response_document ~mime path

  let parent_path =
    Re.(compile (seq [ Re.group (seq [ rep1 any; char '/' ]); rep1 any ]))

  let default_listing files req =
    let dirs =
      List.map
        (fun (kind, fname) ->
          let name = Format.asprintf "%a %s" pp_kind kind fname in
          Filename.concat (Request.target req) fname |> Gemtext.link ~name)
        files
    in
    let title =
      Request.param req 1 |> Printf.sprintf "Index: %s" |> Gemtext.heading `H1
    in
    let menu =
      if Request.target req = "" then title :: dirs
      else
        match Request.uri req |> Uri.to_string |> Re.exec_opt parent_path with
        | None -> title :: dirs
        | Some grp ->
            let name =
              Format.asprintf "%a Parent directory" pp_kind `Directory
            in
            let link = Re.Group.get grp 1 |> Gemtext.link ~name in
            title :: link :: Gemtext.newline :: dirs
    in
    menu |> Response.response_gemtext |> Dir.IO.return

  let read_dir ~show_hidden ~index path =
    let* files = Dir.read path in
    List.fold_left
      (fun acc fname ->
        let* acc = acc in
        if String.equal fname index then
          `Index (Dir.concat path fname) |> Dir.IO.return
        else
          match acc with
          | `Index _ -> Dir.IO.return acc
          | `Filenames fnames ->
              if (not show_hidden) && String.starts_with ~prefix:"." fname then
                `Filenames fnames |> Dir.IO.return
              else
                let* kind = Dir.concat path fname |> Dir.kind in
                `Filenames ((kind, fname) :: fnames) |> Dir.IO.return)
      (`Filenames [] |> Dir.IO.return)
      files

  let reference_parent path =
    String.fold_left
      (fun (acc, dot) -> function
        | '.' when dot -> (true, dot)
        | '.' -> (acc, true)
        | _ -> (acc, dot))
      (false, false) path
    |> fst

  let not_found = Response.(response Status.not_found "") |> Dir.IO.return

  let static ?(handler = default_handler) ?(dir_listing = default_listing)
      ?(index = "index.gmi") ?(show_hidden = false) base_path req =
    let req_path = Request.param req 1 |> Uri.pct_decode in
    if reference_parent req_path then not_found
    else
      let path = Dir.concat base_path req_path in
      try
        let* is_exists = Dir.exists path in
        if is_exists then
          let* kind = Dir.kind path in
          match kind with
          | `Regular_file -> handler path req
          | `Directory ->
              Dir.IO.bind (read_dir ~show_hidden ~index path) (function
                | `Filenames fnames -> dir_listing fnames req
                | `Index index_path -> handler index_path req)
          | `Other -> not_found
        else not_found
      with io ->
        Log.warn (fun log -> log "%a" Dir.pp_io_err io);
        not_found
end
OCaml

Innovation. Community. Security.