package vif

  1. Overview
  2. Docs

Source file vif_handler.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
type ('c, 'value) t =
     ('c, string) Vif_request.t
  -> string
  -> Vif_server.t
  -> 'value
  -> (Vif_response.empty, Vif_response.sent, unit) Vif_response.t option

let pwd = Fpath.v (Unix.getcwd ())
let tree = Conan_light.tree

let sha256sum path =
  let path = Fpath.to_string path in
  if Sys.file_exists path = false || Sys.is_directory path then
    invalid_arg "sha256sum";
  let fd = Unix.openfile path Unix.[ O_RDONLY ] 0o644 in
  let finally () = Unix.close fd in
  Fun.protect ~finally @@ fun () ->
  let stat = Unix.fstat fd in
  let ba =
    Unix.map_file fd Bigarray.char Bigarray.c_layout false
      [| stat.Unix.st_size |]
  in
  let ba = Bigarray.array1_of_genarray ba in
  let hash = Digestif.SHA256.digest_bigstring ba in
  Digestif.SHA256.to_hex hash

let mime_type path =
  match Conan_unix.run_with_tree Conan_light.tree (Fpath.to_string path) with
  | Ok m ->
      Option.value ~default:"application/octet-stream" (Conan.Metadata.mime m)
  | Error _ -> "application/octet-stream"
  | exception _ -> "application/octet-stream"

let cached_on_client_side req target =
  let hdrs = Vif_request.headers req in
  let hash = sha256sum target in
  match Vif_headers.get hdrs "if-none-match" with
  | Some hash' -> String.equal hash hash'
  | None -> false

let valid ~top target =
  Fpath.is_prefix top target
  && Sys.file_exists (Fpath.to_string target)
  && Sys.is_directory (Fpath.to_string target) = false

let pp_msg ppf (`Msg msg) = Fmt.string ppf msg

let trim lst =
  let lst = List.drop_while (( = ) "") lst in
  let lst = List.drop_while (( = ) "") (List.rev lst) in
  List.rev lst

module K = struct
  type t = Fpath.t

  let equal = Fpath.equal
  let hash = Hashtbl.hash
end

module V = struct
  type t = { mtime: float; mime: string }

  let weight _ = 1
end

module Cache = Lru.M.Make (K) (V)

let cached_on_server_size stat abs_path cache =
  match Cache.find abs_path cache with
  | Some { mtime; mime } when mtime >= stat.Unix.st_mtime -> Some mime
  | Some _ ->
      Cache.remove abs_path cache;
      None
  | None -> None

let static ?(top = pwd) =
  ();
  let cache = Cache.create ~random:true 0x100 in
  fun req target _server _ ->
    let target = String.split_on_char '/' target in
    let target = trim target in
    let target = String.concat "/" target in
    let abs_path =
      let ( let* ) = Result.bind in
      let* x = Fpath.of_string target in
      Ok Fpath.(normalize (top // x))
    in
    match (Vif_request.meth req, abs_path) with
    | `GET, Ok abs_path when valid ~top abs_path -> begin
        let ( let* ) = Vif_response.bind in
        let process =
          if cached_on_client_side req abs_path then
            let* () = Vif_response.with_string req "" in
            Vif_response.respond `Not_modified
          else
            let stat = Unix.stat (Fpath.to_string abs_path) in
            let mime =
              match cached_on_server_size stat abs_path cache with
              | Some mime -> mime
              | None ->
                  let mime = mime_type abs_path in
                  let value = { V.mtime= stat.Unix.st_mtime; mime } in
                  Cache.add abs_path value cache;
                  mime
            in
            let src = Vif_stream.Source.file (Fpath.to_string abs_path) in
            let field = "content-length" in
            let size = string_of_int stat.Unix.st_size in
            let* () = Vif_response.add ~field size in
            let field = "content-type" in
            let* () = Vif_response.add ~field mime in
            let field = "etag" in
            let* () = Vif_response.add ~field (sha256sum abs_path) in
            let* () = Vif_response.with_source req src in
            Vif_response.respond `OK
        in
        Some process
      end
    | _ -> None
OCaml

Innovation. Community. Security.