package eliom

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file eliom_route_base.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# 1 "src/lib/eliom_route_base.shared.ml"
(* Ocsigen
 * http://www.ocsigen.org
 * Module eliommod_services.ml
 * 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.
 *)

open Eliom_lib
open Lwt

let section = Lwt_log.Section.make "eliom:service"

module type PARAM = sig
  type site_data
  type info
  type params
  type result

  val sess_info_of_info : info -> Eliom_common.sess_info
  val meth_of_info : info -> Eliom_common.meth
  val subpath_of_info : info -> string list

  val make_params :
     site_data
    -> info
    -> string list option
    -> Eliom_common.full_state_name option
    -> params

  val handle_directory : info -> result Lwt.t
  val get_number_of_reloads : unit -> int

  module Node : sig
    type t

    val up : t -> unit
    val remove : t -> unit
  end

  module Table : sig
    type t

    val empty : unit -> t

    val add :
       Eliom_common.page_table_key
      -> Node.t option * (params, result) Eliom_common.service list
      -> t
      -> t

    val find :
       Eliom_common.page_table_key
      -> t
      -> Node.t option * (params, result) Eliom_common.service list

    val remove : Eliom_common.page_table_key -> t -> t
  end

  module Container : sig
    type t

    val set_contains_timeout : t -> bool -> unit

    val dlist_add :
       ?sp:Eliom_common.server_params
      -> t
      -> ( Table.t ref * Eliom_common.page_table_key
           , Eliom_common.na_key_serv )
           Eliom_lib.leftright
      -> Node.t

    val get : t -> (int * int * Table.t Eliom_common.dircontent ref) list

    val set :
       t
      -> (int * int * Table.t Eliom_common.dircontent ref) list
      -> unit
  end
end

module Make (P : PARAM) = struct
  let find_page_table nosuffixversion now (pagetableref : P.Table.t ref)
      fullsessname (site_data : P.site_data) (info : P.info)
      (urlsuffix : _ option) k : P.result Lwt.t
    =
    let sp = P.make_params site_data info urlsuffix fullsessname in
    Lwt.catch
      (fun () -> Lwt.return (P.Table.find k !pagetableref))
      (function Not_found -> fail Eliom_common.Eliom_404 | e -> fail e)
    >>= fun (node, l) ->
    let rec aux toremove = function
      | [] ->
          Lwt.return
            (Eliom_common.Notfound Eliom_common.Eliom_Wrong_parameter, [])
      | ({Eliom_common.s_max_use; s_expire; s_f; _} as a) :: l -> (
        match s_expire with
        | Some (_, e) when !e < now ->
            (* Service expired. Removing it. *)
            Lwt_log.ign_info ~section "Service expired. Removing it";
            aux toremove l >>= fun (r, toremove) -> Lwt.return (r, a :: toremove)
        | _ ->
            catch
              (fun () ->
                 Lwt_log.ign_info ~section "Trying a service";
                 s_f nosuffixversion sp >>= fun p ->
                 (* warning: the list ll may change during funct
                  if funct register something on the same URL!! *)
                 Lwt_log.ign_info ~section
                   "Page found and generated successfully";
                 (* If this is an anonymous coservice,
                  we place it at the top of the dlist
                  (limitation of number of coservices) *)
                 (match node with None -> () | Some node -> P.Node.up node);
                 (* We update the expiration date *)
                 (match s_expire with
                 | Some (timeout, e) -> e := timeout +. now
                 | None -> ());
                 let newtoremove =
                   match s_max_use with
                   | Some s_max_use ->
                       if s_max_use = 1
                       then a :: toremove
                       else (
                         a.s_max_use <- Some (s_max_use - 1);
                         toremove)
                   | _ -> toremove
                 in
                 Lwt.return (Eliom_common.Found p, newtoremove))
              (function
                 | Eliom_common.Eliom_Wrong_parameter ->
                     aux toremove l >>= fun (r, toremove) ->
                     Lwt.return (r, toremove)
                 | e -> Lwt.return (Eliom_common.Notfound e, toremove)))
    in
    aux [] l >>= fun (r, toremove) ->
    (match node, toremove with
    | _, [] -> ()
    | Some node, _ ->
        (* it is an anonymous coservice that has expired.
                          We remove it form the dlist.
                          This will do the removal from this table
                          automatically.
                          Note that in that case, toremove has length 1
                          (like the initial list l).
        *)
        P.Node.remove node
    | None, _ -> (
      (* removing manually *)
      try
        let _, l = P.Table.find k !pagetableref
        and newptr = P.Table.remove k !pagetableref in
        (* We do find once again because it may have changed! *)
        let newlist =
          List.fold_left
            (fun l a -> List.remove_first_if_any_q a l)
            (* physical equality! *)
            l toremove
        in
        pagetableref :=
          match newlist with
          | [] -> newptr
          | newlist -> P.Table.add k (None, newlist) newptr
      with Not_found -> ()));
    match r with
    | Eliom_common.Found r -> Lwt.return (r : P.result)
    | Eliom_common.Notfound e -> fail e

  let remove_id services id =
    List.filter (fun {Eliom_common.s_id; _} -> s_id <> id) services

  let find_and_remove_id services id =
    let found, l =
      let f (found, l) ({Eliom_common.s_id; _} as x) =
        if id = s_id then Some x, l else found, x :: l
      in
      List.fold_left f (None, []) services
    in
    match found with Some found -> found, List.rev l | None -> raise Not_found

  let add_page_table tables url_act tref key
      ({Eliom_common.s_id; s_expire; _} as service)
    =
    let sp = Eliom_common.get_sp_option () in
    (match s_expire with
    | Some _ -> P.Container.set_contains_timeout tables true
    | _ -> ());
    (* Duplicate registration forbidden in global table with same generation *)
    match key with
    | {Eliom_common.key_state = Eliom_common.SAtt_anon _, _; key_meth = `Get}
    | { Eliom_common.key_state = _, Eliom_common.SAtt_anon _
      ; key_meth = `Post | `Put | `Delete } -> (
      (* Anonymous coservice:
         - only one for each key
         - we add a node in the dlist to limit their number *)
      try
        let (nodeopt, _), newt =
          P.Table.find key !tref, P.Table.remove key !tref
        in
        (match nodeopt with
        | None -> () (* should not occur *)
        | Some node -> P.Node.up node);
        tref := P.Table.add key (nodeopt, [service]) newt
      with Not_found ->
        let node = P.Container.dlist_add ?sp tables (Left (tref, key)) in
        tref := P.Table.add key (Some node, [service]) !tref)
    | {Eliom_common.key_state = Eliom_common.SAtt_no, Eliom_common.SAtt_no; _}
      -> (
      try
        let _nodeopt, l = P.Table.find key !tref
        and newt = P.Table.remove key !tref in
        (* nodeopt should be None *)
        try
          (* verify that we haven't registered something similar *)
          let _, oldl = find_and_remove_id l s_id in
          (* if there was an old version with the same id, we remove
              it? *)
          if sp = None
          then
            (* but if there was already one with same generation, we
                fail (if during initialisation) *)
            raise
              (Eliom_common.Eliom_duplicate_registration
                 (Url.string_of_url_path ~encode:false url_act))
          else
            (* We insert as last element so that services are tried
                in registration order *)
            tref := P.Table.add key (None, oldl @ [service]) newt
        with Not_found -> tref := P.Table.add key (None, l @ [service]) newt
      with Not_found -> tref := P.Table.add key (None, [service]) !tref)
    | _ -> (
      try
        let _nodeopt, l = P.Table.find key !tref
        and newt = P.Table.remove key !tref in
        let _, oldl = find_and_remove_id l s_id in
        (* if there was an old version with the same id, we remove it *)
        tref := P.Table.add key (None, oldl @ [service]) newt
      with Not_found -> tref := P.Table.add key (None, [service]) !tref)

  let remove_page_table _ _ tref key id =
    (* Actually this does not remove empty directories.
       But this will be done by the next service GC *)
    let nodeopt, l = P.Table.find key !tref in
    match nodeopt with
    | Some node ->
        (* In that case, l has size 1, and the id is correct,
         because it is an anonymous coservice *)
        (*VVV the key is searched twice *)
        P.Node.remove node
    | None -> (
        let newt = P.Table.remove key !tref in
        match remove_id l id with
        | [] -> tref := newt
        (* In that case, we must remove it, otherwise we get
         "Wrong parameters" instead of "404 Not found" *)
        | newl -> tref := P.Table.add key (None, newl) newt)

  let add_dircontent dc (key, (elt : P.Table.t Eliom_common.direlt ref)) =
    match dc with
    | Eliom_common.Vide ->
        Eliom_common.Table (String.Table.add key elt String.Table.empty)
    | Eliom_common.Table t -> Eliom_common.Table (String.Table.add key elt t)

  let find_dircontent dc k =
    match dc with
    | Eliom_common.Vide -> raise Not_found
    | Eliom_common.Table t -> String.Table.find k t

  let add_or_remove_service f tables table url_act page_table_key va =
    let rec aux dircontentref a l =
      try
        let direltref = find_dircontent !dircontentref a in
        match !direltref with
        | Eliom_common.Dir dcr -> search_page_table_ref dcr l
        | Eliom_common.File _ -> raise (Eliom_common.Eliom_page_erasing a)
      with Not_found ->
        let newdcr = ref (Eliom_common.empty_dircontent ()) in
        dircontentref :=
          add_dircontent !dircontentref (a, ref (Eliom_common.Dir newdcr));
        search_page_table_ref newdcr l
    and search_page_table_ref dircontentref = function
      | [] | [""] ->
          search_page_table_ref dircontentref [Eliom_common.defaultpagename]
      | [a] -> (
        try
          let direltref = find_dircontent !dircontentref a in
          match !direltref with
          | Eliom_common.Dir _ -> raise (Eliom_common.Eliom_page_erasing a)
          | Eliom_common.File ptr -> ptr
        with Not_found ->
          let newpagetableref = ref (P.Table.empty ()) in
          dircontentref :=
            add_dircontent !dircontentref
              (a, ref (Eliom_common.File newpagetableref));
          newpagetableref)
      | "" :: l -> search_page_table_ref dircontentref l
      | a :: l -> aux dircontentref a l
    in
    let page_table_ref = search_page_table_ref table url_act in
    f tables url_act page_table_ref page_table_key va

  let add_service priority tables url_act page_table_key va =
    let generation = P.get_number_of_reloads () in
    let rec find_table = function
      | [] ->
          let t = ref (Eliom_common.empty_dircontent ()) in
          t, [generation, priority, t]
      | (g, p, t) :: _ as l when g = generation && p = priority -> t, l
      | (g, p, _) :: _ as l when g < generation || p < priority ->
          let t = ref (Eliom_common.empty_dircontent ()) in
          t, (generation, priority, t) :: l
      | ((g, p, _) as a) :: l when g = generation && p > priority ->
          let t, ll = find_table l in
          t, a :: ll
      | _ -> assert false
    in
    let table, new_table_services = find_table (P.Container.get tables) in
    P.Container.set tables new_table_services;
    add_or_remove_service add_page_table tables table url_act page_table_key va

  let remove_service tables path k unique_id =
    let rec aux = function
      | [] -> ()
      | (_, _, table) :: l -> (
        try
          add_or_remove_service remove_page_table tables table path k unique_id
        with Not_found -> aux l)
    in
    aux (P.Container.get tables)

  exception Exn1

  let find_service now tables fullsessname sitedata info : P.result Lwt.t =
    let rec search_page_table dircontent : _ -> P.result Lwt.t =
      let find nosuffixversion page_table_ref suffix =
        let si = P.sess_info_of_info info in
        find_page_table nosuffixversion now page_table_ref fullsessname sitedata
          info suffix
          { Eliom_common.key_state =
              ( Eliom_common.att_key_serv_of_req
                  (fst si.Eliom_common.si_state_info)
              , Eliom_common.att_key_serv_of_req
                  (snd si.Eliom_common.si_state_info) )
          ; Eliom_common.key_meth = P.meth_of_info info }
      in
      let aux a l =
        let aa =
          match a with None -> Eliom_common.defaultpagename | Some aa -> aa
        in
        Lwt.catch
          (fun () ->
             let dc =
               try !(find_dircontent dircontent aa)
               with Not_found -> raise Exn1
             in
             match dc with
             | Eliom_common.Dir dircontentref2 ->
                 search_page_table !dircontentref2 l
             | Eliom_common.File page_table_ref -> (
               match l with
               | [] -> find false page_table_ref None
               | _ ->
                   (* We have a file with suffix *)
                   raise Eliom_common.Eliom_Wrong_parameter))
          (function
             | (Exn1 | Eliom_common.Eliom_Wrong_parameter) as e -> (
               (* If no service matches, we try a suffix service *)
               try
                 match
                   !(try
                       find_dircontent dircontent
                         Eliom_common.eliom_suffix_internal_name
                     with Not_found -> raise e)
                 with
                 | Eliom_common.Dir _ -> Lwt.fail Exn1
                 | Eliom_common.File page_table_ref ->
                     find false page_table_ref
                       (if a = None then Some [] else Some (aa :: l))
               with e -> Lwt.fail e)
             | e -> Lwt.fail e)
      in
      function
      | [] ->
          (* It is a directory, without / at the end. We do a redirection. *)
          P.handle_directory info
      | [""] -> aux None []
      | [a] when a = Eliom_common.eliom_nosuffix_page -> (
        (* version without suffix of suffix service *)
        try
          match
            !(try
                find_dircontent dircontent
                  Eliom_common.eliom_suffix_internal_name
              with Not_found -> raise Exn1)
          with
          | Eliom_common.Dir _ -> Lwt.fail Exn1
          | Eliom_common.File page_table_ref -> find true page_table_ref None
        with e -> Lwt.fail e)
      (*      | ""::l -> search_page_table dircontent l *)
      (* We do not remove "//" any more
           because of optional suffixes *)
      | a :: l -> aux (Some a) l
    in
    let search_by_priority_generation tables path =
      (* New in 1.91: There is now one table for each pair
         (generation, priority) *)
      List.fold_left
        (fun prev (_prio, _gen, table) ->
           Lwt.catch
             (fun () -> prev)
             (function
                | Exn1 | Eliom_common.Eliom_404
                | Eliom_common.Eliom_Wrong_parameter ->
                    search_page_table !table path
                | e -> fail e))
        (fail Exn1) tables
    in
    Lwt.catch
      (fun () ->
         search_by_priority_generation (P.Container.get tables)
           (Url.change_empty_list (P.subpath_of_info info)))
      (function Exn1 -> Lwt.fail Eliom_common.Eliom_404 | e -> Lwt.fail e)
end
OCaml

Innovation. Community. Security.