Plasma GitLab Archive
Projects Blog Knowledge

(*
 * <COPYRIGHT>
 * Copyright 2002 Joachim Schrod Network and Publication Consultance GmbH, Gerd Stolpmann
 *
 * <GPL>
 * This file is part of WDialog.
 *
 * WDialog is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * WDialog 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WDialog; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * </>
 *)

(* $Id: wd_dialog.ml,v 3.12 2005-06-11 14:24:14 stolpmann Exp $
 * ----------------------------------------------------------------------
 *
 *)

open Wd_types
open Wd_serialize

let revision_dialog = "$Revision: 3.12 $" ;;
  (* intentionally the CVS revision string *)

let serialize_interactor f b ia =
  let b' = Buffer.create 100 in
  Wd_interactor.serialize f b' ia;
  let l = Buffer.length b' in
  Buffer.add_string b "I(";
  Buffer.add_string b (string_of_int l);
  Buffer.add_string b ")";
  Buffer.add_buffer b b'
;;


let serialize_string_option_interactor =
  serialize_interactor 
    (function
       | None -> "0"
       | Some s -> "S(" ^ string_of_int(String.length s) ^ ")" ^ s
    )
;;


let serialize_unit_interactor =
  serialize_interactor
    (fun () -> "0")
;;


let serialize_enumvars b l =
  Buffer.add_string b "L(3,";
  Buffer.add_string b (string_of_int (List.length l));
  Buffer.add_string b ")";
  List.iter
    (fun (s1,s2_opt,s3) ->
       serialize_string b s1;
       ( match s2_opt with
	   | None -> Buffer.add_string b "0"
	   | Some s2 -> serialize_string b s2
       );
       serialize_string b s3;
    )
    l
;;


let unserialize_interactor f buf =
  let tok = ds_scan_token buf in
  match tok with
    | I_tok n ->
	let e = buf.ds_pos + n in
	if e > buf.ds_end then failwith "unserialize_interactor";
	let buf' = { buf with ds_end = e } in
	let ia = Wd_interactor.unserialize f buf' in
	if buf'.ds_pos <> e then failwith "unserialize_interactor";
	buf.ds_pos <- e;
	ia
    | _ ->
	failwith "unserialize_interactor"
;;


let unserialize_string_option_interactor =
  unserialize_interactor unserialize_string_option
;;


let unserialize_unit_interactor =
  unserialize_interactor unserialize_unit
;;


let unserialize_enumvars buf =
  let tok = ds_scan_token buf in
  match tok with
    | L_tok(3,n) ->
	ds_make_list n
	  (fun () ->
	     let s1 = unserialize_string buf in
	     let s2_opt = unserialize_string_option buf in
	     let s3 = unserialize_string buf in
	     (s1,s2_opt,s3))
    | _ -> failwith "unserialize_enumvars"
;;



let dump_interactors out ia =
  let dump_goto_ia ia =
    Wd_interactor.iter
      (fun id name index value ->
	 let v = match value with
	     None   -> "None"
	   | Some s -> "Some " ^ s
	 in
	 Format.fprintf out "  id='%s' name='%s' index='%s' goto='%s'@\n"
	   id name index v)
      ia
  in
  let dump_unit_ia ia =
    Wd_interactor.iter
      (fun id name index value ->
	 Format.fprintf out "  id='%s' name='%s' index='%s'@\n"
	   id name index)
      ia
  in

  Format.fprintf out "Buttons:@\n";
  dump_goto_ia ia.ui_buttons;
  Format.fprintf out "Imagebuttons:@\n";
  dump_goto_ia ia.ui_imagebuttons;
  Format.fprintf out "Anchors:@\n";
  dump_goto_ia ia.ui_anchors;
  Format.fprintf out "Indexed buttons:@\n";
  dump_goto_ia ia.ui_indexed_buttons;
  Format.fprintf out "Indexed imagebuttons:@\n";
  dump_goto_ia ia.ui_indexed_imagebuttons;
  Format.fprintf out "Indexed anchors:@\n";
  dump_goto_ia ia.ui_indexed_anchors;
  Format.fprintf out "Variables:@\n";
  dump_unit_ia ia.ui_vars;
  Format.fprintf out "Uploads:@\n";
  dump_unit_ia ia.ui_uploads;
  Format.fprintf out "Enumerated values:@\n";
  List.iter
    (function
	 (name, None, pg)       -> Format.fprintf out "  name='%s' page='%s'@\n"
	                             name pg;
       | (name, Some index, pg) -> Format.fprintf out "  name='%s' index='%s' page='%s'@\n"
	                             name index pg
    )
    ia.ui_enumvars
;;


let dot_re = Netstring_pcre.regexp "[.]";;

exception Check_dot;;

class virtual dialog
              init_universe init_dlg_name init_env =
  object (self : 'self)

    val environment = init_env
    val dialog_name = init_dlg_name
    val universe = (init_universe : universe_type)
    val application = init_universe # application
    val declaration =
	    try init_universe # application # dialog_declaration init_dlg_name
	    with Not_found ->
	      raise(Runtime_error ("Dialog not found: " ^  init_dlg_name))

    val mutable page_name = ""
    val mutable next_page = ""

    val mutable variables = (Wd_dictionary.empty : var_value dict)
    val mutable last_event = No_event
    val mutable server_popup_request = false

    val mutable interactors =
	    { ui_buttons = Wd_interactor.create None;
	      ui_imagebuttons = Wd_interactor.create None;
	      ui_anchors = Wd_interactor.create None;
	      ui_indexed_buttons = Wd_interactor.create None;
	      ui_indexed_imagebuttons = Wd_interactor.create None;
	      ui_indexed_anchors = Wd_interactor.create None;
	      ui_vars = Wd_interactor.create ();
	      ui_uploads = Wd_interactor.create ();
	      ui_enumvars = [];
	    };

    val mutable upload_manager = lazy ( assert false )

    val mutable session = None

    initializer
      self # init (declaration # start_page);

    method copy =
      ( Oo.copy self : #dialog_type :> dialog_type )


    method private init_defaults =
      (* Add the default values to 'variables' *)
      (* NOTE: As object variables do not allow to specify defaults, these
       * are left out.
       *)
      List.iter
	(fun name ->
	   let v = declaration # variable name in
	   if not (Wd_dictionary.mem v.var_name variables) then begin
	     match v.var_default with
		 Some default ->
		   self # set_variable v.var_name default
	       | None ->
		   if v.var_associative then
		     self # set_variable v.var_name (Alist_value [])
		   else begin
		     match v.var_type with
			 String_type ->
			   self # set_variable v.var_name (String_value "")
		       | Enum_type _ ->
			   self # set_variable v.var_name (Enum_value [])
		       | Dyn_enum_type ->
			   self # set_variable v.var_name (Dyn_enum_value [])
		       | Dialog_type ->
			   self # set_variable v.var_name (Dialog_value None)
		   end
	   end
	)
	(declaration # variable_names)


    method init pg =
      (* Go to page 'pg' *)
      if not (List.mem pg (declaration # page_names)) then
	raise (Runtime_error ("Page `" ^ pg ^ "' does not exist"));
      page_name <- pg;
      next_page <- pg;
      (* last_event <- No_event; *)
      (* Call Wd_interactor.clear now to delete all interactors. The
       * sequence generators for the automatically generated IDs remain
       * intact.
       *)
      Wd_interactor.clear interactors.ui_buttons;
      Wd_interactor.clear interactors.ui_imagebuttons;
      Wd_interactor.clear interactors.ui_anchors;
      Wd_interactor.clear interactors.ui_indexed_buttons;
      Wd_interactor.clear interactors.ui_indexed_imagebuttons;
      Wd_interactor.clear interactors.ui_indexed_anchors;
      Wd_interactor.clear interactors.ui_vars;
      Wd_interactor.clear interactors.ui_uploads;
      interactors.ui_enumvars <- [];
      upload_manager <- lazy (raise(Runtime_error("Cannot access uploaded files at this stage of request processing")));
      self # init_defaults;


    method page_names =
      declaration # page_names


    method name =
      dialog_name


    method page_name =
      page_name


    method next_page =
      next_page


    method set_next_page pgname =
      if not (List.mem pgname (declaration # page_names)) then
	raise (Runtime_error ("Page `" ^ pgname ^ "' does not exist"));
      next_page <- pgname;


    method interactors = interactors

    method variable name =
      let rec descend dlg dn =
	match dn with
	    [] -> assert false
	  | [n] -> dlg # variable n
	  | n::dn' -> 
	      ( match dlg # dialog_variable n with
		    None -> 
		      raise (Runtime_error ("Dialog `" ^ n ^ "' is empty"))
		  | Some dlg' ->
		      descend dlg' dn'
	      )
      in
      try
	Wd_dictionary.find name variables
      with
	  Not_found ->
	    (* Maybe the dot notation is used: *)
	    let names = Netstring_pcre.split dot_re name in
	    match names with
		[] -> raise (No_such_variable name)
	      | [_] -> raise (No_such_variable name)
	      | _ -> descend (self :> dialog_type) names


    method string_variable name =
      match self # variable name with
	  String_value s -> s
	| _ ->
	    raise (Runtime_error ("Variable `" ^ name ^ "' accessed as string"))


    method enum_variable name =
      match self # variable name with
	  Enum_value s -> s
	| _ ->
	    raise (Runtime_error ("Variable `" ^ name ^ "' accessed as enum"))


    method dyn_enum_variable name =
      (* it is possible to read an "enum_variable" also as "dyn_enum_variable"*)
      self # read_dyn_enum name (self # variable name)


    method variable_decl name =
      let rec descend dlg dn =
	match dn with
	    [] -> assert false
	  | [n] -> dlg # declaration # variable n
	  | n::dn' -> 
	      ( match dlg # dialog_variable n with
		    None -> 
		      raise (Runtime_error ("Dialog `" ^ n ^ "' is empty"))
		  | Some dlg' ->
		      descend dlg' dn'
	      )
      in
      try declaration # variable name
      with
	  Not_found ->
	    (* Maybe the dot notation is used: *)
	    let names = Netstring_pcre.split dot_re name in
	    match names with
		[] -> raise Not_found
	      | [_] -> raise Not_found
	      | _ -> descend (self :> dialog_type) names


    method private read_dyn_enum name value =
      match value with
	  Dyn_enum_value s -> s
	| Enum_value s ->
	    (* 's' contains only the internal values *)
	    let vt =
	      try
		(self # variable_decl name).var_type
	      with
		  Not_found ->
		    raise (Runtime_error ("Cannot read non-declared variable `" ^
				    name ^ "'"))
	    in
	    begin match vt with
		Enum_type e ->
		  List.map
		    (fun internal ->
		       try internal, List.assoc internal e.enum_definition
		       with Not_found -> assert false)
		    s
	      | _ -> assert false
	    end
	| _ ->
	    raise (Runtime_error ("Variable `" ^ name ^ "' accessed as dyn enum"))


    method dialog_variable name =
      match self # variable name with
	  Dialog_value s -> s
	| _ ->
	    raise (Runtime_error ("Variable `" ^ name ^ "' accessed as dialog"))


    method alist_variable name =
      match self # variable name with
	  Alist_value s -> s
	| _ ->
	    raise (Runtime_error ("Variable `" ^ name ^ "' accessed as alist"))

    method lookup_string_variable name index =
      let al = self # alist_variable name in
      match List.assoc index al with
	  String_value s -> s
	| _ ->
	    raise (Runtime_error ("Variable `" ^ name ^ "' accessed as string"))


    method lookup_enum_variable name index =
      let al = self # alist_variable name in
      match List.assoc index al with
	  Enum_value s -> s
	| _ ->
	    raise (Runtime_error ("Variable `" ^ name ^ "' accessed as enum"))


    method lookup_dyn_enum_variable name index =
      let al = self # alist_variable name in
      match List.assoc index al with
	  (Dyn_enum_value _ | Enum_value _) as v ->
	    self # read_dyn_enum name v
	| _ ->
	    raise (Runtime_error ("Variable `" ^ name ^ "' accessed as dyn enum"))


    method lookup_dialog_variable name index =
      let al = self # alist_variable name in
      match List.assoc index al with
	  Dialog_value s -> s
	| _ ->
	    raise (Runtime_error ("Variable `" ^ name ^ "' accessed as dialog"))



    method set_variable name value =
      let rec descend dlg dn =
	match dn with
	    [] -> assert false
	  | [n] -> dlg # set_variable n value
	  | n::dn' -> 
	      ( match dlg # dialog_variable n with
		    None -> 
		      raise (Runtime_error ("Dialog `" ^ n ^ "' is empty"))
		  | Some dlg' ->
		      descend dlg' dn'
	      )
      in
      try
	let var =
	  try
	    declaration # variable name
	  with
	    Not_found ->
	      (* Maybe the dot notation is used: *)
	      raise Check_dot
	in
	begin try
      	  self # check_type value var false
	with
	    Runtime_error s ->
	      raise (Runtime_error ("While setting variable `" ^ name ^ "':" ^
				    s))
	end;
	(* Set variable: *)
	variables <- Wd_dictionary.add name value variables
      with
	  Check_dot ->
	    let names = Netstring_pcre.split dot_re name in
	    match names with
		[] -> raise (No_such_variable name)
	      | [_] -> raise (No_such_variable name)
	      | _ -> descend (self :> dialog_type) names


    method private check_type value var inner_check =
      (* Check type: *)
      let vt = var.var_type in
      let assoc = var.var_associative in
      begin match value with
	  String_value _ ->
	    if assoc & not inner_check then
	      raise (Runtime_error ("Cannot put plain value into associative variable without index"));
	    if vt <> String_type then
	      raise (Runtime_error ("Cannot put a string into variable"))
	| Dialog_value _ ->
	    if assoc & not inner_check then
	      raise (Runtime_error ("Cannot put plain value into associative variable without index"));
	    if vt <> Dialog_type then
	      raise (Runtime_error ("Cannot put a dialog into variable"));
	| Dyn_enum_value v ->
	    if assoc & not inner_check then
	      raise (Runtime_error ("Cannot put plain value into associative variable without index"));
	    if vt <> Dyn_enum_type then
	      raise (Runtime_error ("Cannot put a dynamic enumeration into variable"));
	| Enum_value evals ->
	    if assoc & not inner_check then
	      raise (Runtime_error ("Cannot put plain value into associative variable without index"));
	    begin match vt with
		Enum_type e ->
		  if List.exists
		       (fun v -> not (List.mem_assoc v e.enum_definition))
		        evals
		  then
		    raise (Runtime_error ("Values are not compatible with variable"));
	      | _ ->
		  raise (Runtime_error ("Cannot put an enumeration into variable"));
	    end
	| Alist_value al ->
	    if not assoc then
	      raise (Runtime_error ("Cannot put an Alist value into a plain variable"));
	    List.iter
	      (fun (n,v) ->
		 self # check_type v var true)
	      al;
      end;


    method private t_get =
      Wd_template.get application
    method private t_apply =
      Wd_template.apply (self : #dialog_type :> dialog_type)
    method private t_apply_byname =
      Wd_template.apply_byname (self : #dialog_type :> dialog_type)
    method private t_apply_lazily =
      Wd_template.apply_lazily (self : #dialog_type :> dialog_type)
    method private t_concat =
      Wd_template.concat application
    method private t_empty =
      Wd_template.empty application
    method private t_text =
      Wd_template.text application
    method private t_html =
      Wd_template.html application
    method private t_to_string =
      Wd_template.to_string (self : #dialog_type :> dialog_type)


    method private put_tree n t =
      self # set_variable n
	(String_value
	   (Wd_template.to_string (self : #dialog_type :> dialog_type) t))


    method unset_variable name =
      let rec descend dlg dn =
	match dn with
	    [] -> assert false
	  | [n] -> dlg # unset_variable n
	  | n::dn' -> 
	      ( match dlg # dialog_variable n with
		    None -> 
		      raise (Runtime_error ("Dialog `" ^ n ^ "' is empty"))
		  | Some dlg' ->
		      descend dlg' dn'
	      )
      in
      (* Sets the value of 'name' to the default value. *)
      if List.mem name (declaration # variable_names) then begin
	variables <- Wd_dictionary.remove name variables;
	self # init_defaults;
      end
      else begin
	(* Maybe the dot notation is used: *)
	let names = Netstring_pcre.split dot_re name in
	match names with
	    [] -> raise (No_such_variable name)
	  | [_] -> raise (No_such_variable name)
	  | _ -> descend (self :> dialog_type) names
      end


    method lookup_uploaded_file name =
      let arg = Wd_upload.get (Lazy.force upload_manager) name in
	        (* or raise Not_found *)
      let fn = arg # filename in
      if fn = None or fn = Some "" then
	None
      else
	Some arg


    method event =
      last_event

    method set_event ev =
      last_event <- ev

    method is_server_popup_request =
      server_popup_request

    method set_server_popup_request() =
      server_popup_request <- true

    method serialize m b =
      (* Do not serialize temporary variables. *)
      let persistent_variables =
	List.filter
	  (fun (name,value) ->
	     try
	       not (declaration # variable name).var_temporary
	     with
		 Not_found -> assert false)
	  (Wd_dictionary.to_alist variables) in

      let cgienv = environment.cgi # environment in

      serialize_string b page_name;
      serialize_string b next_page;
      serialize_event b last_event;
      serialize_string b (cgienv # cgi_remote_addr);   (* IP address *)
      serialize_alist m b persistent_variables;
      serialize_string_option_interactor b interactors.ui_buttons;
      serialize_string_option_interactor b interactors.ui_imagebuttons;
      serialize_string_option_interactor b interactors.ui_anchors;
      serialize_string_option_interactor b interactors.ui_indexed_buttons;
      serialize_string_option_interactor b interactors.ui_indexed_imagebuttons;
      serialize_string_option_interactor b interactors.ui_indexed_anchors;
      serialize_unit_interactor b interactors.ui_vars;
      serialize_enumvars b interactors.ui_enumvars;
      serialize_unit_interactor b interactors.ui_uploads;


    method unserialize buf =
      let s_page_name = unserialize_string buf in
      let s_next_page = unserialize_string buf in
      let s_last_event = unserialize_event buf in
      let last_peer = unserialize_string buf in
      let serialized_variables = unserialize_alist buf in
      let s_ui_buttons = unserialize_string_option_interactor buf in
      let s_ui_imagebuttons = unserialize_string_option_interactor buf in
      let s_ui_anchors = unserialize_string_option_interactor buf in
      let s_ui_indexed_buttons = unserialize_string_option_interactor buf in
      let s_ui_indexed_imagebuttons = unserialize_string_option_interactor buf in
      let s_ui_indexed_anchors = unserialize_string_option_interactor buf in
      let s_ui_vars = unserialize_unit_interactor buf in
      let s_ui_enumvars = unserialize_enumvars buf in
      let s_ui_uploads = unserialize_unit_interactor buf in
      
      let cgienv = environment.cgi # environment in
      let peer = cgienv # cgi_remote_addr in  (* IP address *)

      if peer <> last_peer then
	failwith "Session includes the wrong IP address";

      page_name <- s_page_name;
      next_page <- s_next_page;
      last_event <- s_last_event;
      interactors <- { ui_buttons = s_ui_buttons;
		     ui_imagebuttons = s_ui_imagebuttons;
		     ui_anchors = s_ui_anchors;
		     ui_indexed_buttons = s_ui_indexed_buttons;
		     ui_indexed_imagebuttons = s_ui_indexed_imagebuttons;
		     ui_indexed_anchors = s_ui_indexed_anchors;
		     ui_vars = s_ui_vars;
		     ui_enumvars = s_ui_enumvars;
		     ui_uploads = s_ui_uploads };
      upload_manager <- lazy ( Wd_upload.init environment interactors );
      variables <- Wd_dictionary.of_alist serialized_variables;
      self # init_defaults


    method dump (f:Format.formatter) =
      Format.fprintf f "<dialog-value page=\"%s\" name=\"%s\">@\n  @[<v 0>"
	               (Wd_transform.escape_html page_name)
	               (Wd_transform.escape_html dialog_name);

      let rec print_value value =
	match value with
	    String_value u ->
	      Format.fprintf f "<string-value>%s</string-value>"
		               (Wd_transform.escape_html u)
	  | Enum_value l ->
	      Format.fprintf f "<enum-value>@\n  @[<v 0>";
	      let is_first = ref true in
	      List.iter
		(fun item ->
		   if not !is_first then Format.fprintf f "@\n";
		   is_first := false;
		   Format.fprintf f "<enum-item internal=\"%s\"/>"
                                    (Wd_transform.escape_html item);
		)
		l;
	      Format.fprintf f "@]@\n</enum-value>";
	  | Dyn_enum_value e ->
	      Format.fprintf f "<dyn-enum-value>@\n  @[<v 0>";
	      let is_first = ref true in
	      List.iter
		(fun (int_item, ext_item) ->
		   if not !is_first then Format.fprintf f "@\n";
		   is_first := false;
		   Format.fprintf f
		     "<dyn-enum-item internal=\"%s\" external=\"%s\"/>"
		     (Wd_transform.escape_html int_item)
		     (Wd_transform.escape_html ext_item);
		)
		e;
	      Format.fprintf f "@]@\n</dyn-enum-value>";
	  | Dialog_value None ->
	      Format.fprintf f "<no-dialog-value/>"
	  | Dialog_value (Some o) ->
	      o # dump f
	  | Alist_value a ->
	      Format.fprintf f "<alist-value>@\n  @[<v 0>";
	      let is_first = ref true in
	      List.iter
		(fun (item, value) ->
		   if not !is_first then Format.fprintf f "@\n";
		   is_first := false;
		   Format.fprintf f
		     "<alist-item index=\"%s\">@\n  @[<v 0>"
		     (Wd_transform.escape_html item);
		   print_value value;
		   Format.fprintf f "@]@\n</alist-item>";
		)
		a;
	      Format.fprintf f "@]@\n</alist-value>";
      in

      Wd_dictionary.iter
	(fun name var ->
	   Format.fprintf f "<variable name=\"%s\">@\n  @[<v 0>"
	                    (Wd_transform.escape_html name);
	   print_value var;
	   Format.fprintf f "@]@\n</variable>@\n";
	)
	variables;
      let s = match last_event with
	  No_event -> "no event"
	| Button u -> "button " ^ u ^ " pressed"
	| Image_button (u,x,y) ->
	    Printf.sprintf "image button %s pressed at (%d,%d)" u x y
	| Indexed_button(u,index) -> "button " ^ u ^ "," ^ index ^ " pressed"
	| Indexed_image_button (u,index,x,y) ->
	    Printf.sprintf "image button %s,%s pressed at (%d,%d)" u index x y
	| Popup_request s ->
	    Printf.sprintf "popup request `%s'" s

      in
      Format.fprintf f "<interactors>@\n  @[<v 0>";
      let ia_raw_string =
	Marshal.to_string interactors [] in
      let ia_string =
	Netencoding.Base64.encode
	  ~linelength:72
	  ia_raw_string
      in
      let ia_lines =
	Netstring_str.split (Netstring_str.regexp "\n") ia_string in
      let is_first = ref true in
      List.iter
	(fun line ->
	   if not !is_first then Format.fprintf f "@\n";
	   is_first := false;
	   Format.pp_print_string f line;
	)
	ia_lines;
      Format.fprintf f "@]@\n</interactors>@\n";

      Format.fprintf f "<!--@\n";
      dump_interactors f interactors;
      Format.fprintf f "-->@\n";

      Format.fprintf f "<!-- Last event: %s -->" (Wd_transform.escape_html s);
      (* TODO: interactors *)
      Format.fprintf f "@]@\n</dialog-value>";

    method enter_session_scope s =
      session <- Some s

    method leave_session_scope () = 
      session <- None

    method session =
      match session with
	  Some s -> s
	| None -> failwith "Session-related function called, but the dialog is currently outside the scope of a session"

    method environment = environment
    method declaration = declaration
    method application = application
    method universe = universe

    method virtual prepare_page : unit -> unit

    method virtual handle : unit -> unit

  end
;;


let revision_hash =
  Netencoding.Base64.encode
    (Digest.string
       (Wd_ocamlversion.revision_ocaml ^ "/" ^ 
	Wd_types.revision_types ^ "/" ^ 
	Wd_interactor.revision_interactor ^ "/" ^ 
	revision_dialog))
;;
(* [revision_hash] changes when any of the components changes the version
 * number. This ensures that format incompatibilities can safely detected.
 * This scheme is even a bit overly strict.
 *)


class instant_session init_dialog : session_type =
  object (self)
    val mutable dialog = (init_dialog : dialog_type)
    val mutable dialog_name = init_dialog # name
    val mutable encoded_session = 
      let b = Buffer.create 1000 in
      init_dialog # serialize (Hashtbl.create 10) b;
      Buffer.contents b

    method dialog_name = dialog # name  (* sic! *)
    method dialog = dialog
    method commit_changes() =
      let b = Buffer.create 1000 in
      dialog # serialize (Hashtbl.create 10) b;
      encoded_session <- Buffer.contents b;
      dialog_name <- dialog # name;
    method serialize =
      Netencoding.Base64.encode dialog_name ^ ":" ^
      revision_hash ^ ":" ^ 
      Netencoding.Base64.encode  ~linelength:64 encoded_session
    method change_dialog dlg =
      dialog <- dlg;
    method session_id =
      failwith "The instant session manager does not provide session IDs"
  end
;;


class instant_session_manager () : session_manager_type =
  object (self)

    method create dlg =
      new instant_session dlg

    method unserialize universe env serialized_session =
      let dlg_name_b64, revision, encoded_session_b64 =
	try
	  let l = String.length serialized_session in
	  let k1 = String.index serialized_session ':' in (* or Not_found *)
	  let k2 = String.index_from serialized_session (k1+1) ':' in
	           (* or Not_found *)
	  (String.sub serialized_session 0 k1,
	   String.sub serialized_session (k1+1) (k2-k1-1),
	   String.sub serialized_session (k2+1) (l-k2-1))
	with
	    Not_found ->
	      failwith "Wd_dialog.instant_session_manager: Cannot decode session data"
      in

      if revision <> revision_hash then
	failwith "Wd_dialog.instant_session_manager: Cannot decode a session string with an unknown format (bad revision number)";

      let dlg_name =
	Netencoding.Base64.decode ~accept_spaces:true dlg_name_b64 in
      let encoded_session =
	Netencoding.Base64.decode ~accept_spaces:true encoded_session_b64 in

      let ds_buf =
	{ ds_str = encoded_session;
	  ds_pos = 0;
	  ds_end = String.length encoded_session;
	  ds_universe = universe;
	  ds_environment = env;
	  ds_dialogs = Hashtbl.create 10
	} in

      let dlg =
	try
	  universe # create env dlg_name
	with
	    Not_found ->
	      raise(Runtime_error("Current dialog `" ^ dlg_name ^
                                  "' is not registered in universe"))
      in
      dlg # unserialize ds_buf;
      new instant_session dlg

  end
;;


exception Invalid_session_checksum
exception Session_not_found
;;


let crlf_re = Netstring_pcre.regexp "[\r\n]";;

class database_session ~update id key (inst_session : session_type) =
object (self)
  val update = update
  val id = id
  val key = key
  val inst_session = inst_session
  val mutable checksum = ""

  method dialog_name = inst_session # dialog_name
  method dialog = inst_session # dialog

  method commit_changes () =
    inst_session # commit_changes();
    let value = inst_session # serialize in
    (* Remove all LFs and CRs, they lead to problems *)
    let value' = Netstring_pcre.global_replace crlf_re "" value in
    checksum <- Digest.to_hex (Digest.string value);
    let () = update id key value' checksum in
    ()
    

  method serialize =
    if checksum = "" then
      checksum <- Digest.to_hex (Digest.string inst_session#serialize);
    string_of_int id ^ ":" ^ key ^ ":" ^ checksum


  method session_id =
    string_of_int id ^ ":" ^ key


  method change_dialog dlg =
    inst_session # change_dialog dlg
end
;;


let id_key_cs_re = Netstring_pcre.regexp "^([0-9]+):([^:]*):([^:]*)$"
;;
                                                                             
class database_session_manager ?(private_key = "")
                               ?(enable_checksum = true)
                               ~allocate ~insert ~update ~lookup () =
object (self)
  inherit instant_session_manager () as super

  val private_key = private_key
  val allocate = allocate
  val insert = insert
  val update = update
  val lookup = lookup

  method create dlg =
    let instant_session = super # create dlg in
    let id = allocate() in
    let cgienv = (dlg # environment).cgi # environment in
    let peer = cgienv # cgi_remote_addr in  (* IP address *)
    let key =
      (* Generate a key that is "random enough": *)
      Digest.to_hex
        (Digest.string
           (string_of_int id ^ ":" ^
	    private_key ^ ":" ^
            string_of_float (Unix.gettimeofday()) ^ ":" ^
            string_of_int (Unix.getpid()) ^ ":" ^
            peer)) in
    let () = insert id key in
    new database_session ~update id key instant_session

  method unserialize universe env id_key_cs =
    let (id, key, cs) =
      match Netstring_pcre.string_match id_key_cs_re id_key_cs 0 with
          None ->
            failwith "Wd_dialog.database_session_manager: Session identifier has wrong format"
        | Some r ->
            (int_of_string (Netstring_pcre.matched_group r 1 id_key_cs),
	     Netstring_pcre.matched_group r 2 id_key_cs,
             Netstring_pcre.matched_group r 3 id_key_cs)
    in
    let value, checksum = lookup id key in
    if enable_checksum && cs <> checksum then raise Invalid_session_checksum;
    new database_session ~update id key (super # unserialize universe env value)
end
;;


(* ======================================================================
 * History:
 *
 * $Log: wd_dialog.ml,v $
 * Revision 3.12  2005-06-11 14:24:14  stolpmann
 * Extension of bracket expressions: many new functions.
 * Functions in bracket expressions may now lazily evaluate their arguments.
 * ui:if and ui:ifvar may refer to any functions defined for bracket
 * expressions.
 * New: ui:ifexpr
 * Parsing of bracket expressions is now done with ulex. Private interfaces
 * of PXP are no longer used for this purpose.
 * Serialization has been rewritten, and it is now safe.
 * Fix: In ui:context there may now be macro references at any location.
 * Also documented all these changes.
 *
 * Revision 3.11  2004/12/12 17:57:32  stolpmann
 * 	Added <q:wd-link> and <q:wd-xlink> to generate links for
 * applications that cannot use Javascript. Limited functionality, however.
 * See stdlib.xml for details.
 *
 * Revision 3.10  2003/03/10 23:23:27  stolpmann
 * 	Another security improvement: The checksum now changes after
 * every web request
 *
 * Revision 3.9  2003/03/10 22:46:34  stolpmann
 * 	the session contains now the IP number of the browser
 *
 * Revision 3.8  2003/03/09 17:08:29  stolpmann
 * 	New class database_session_manager
 *
 * Revision 3.7  2002/11/09 11:41:19  stolpmann
 * 	Fix: ui:select accepts dot notation. A new method
 * variable_decl needs to be defined for dialogs. This method
 * returns the var_decl record and interprets the dot notation.
 *
 * Revision 3.6  2002/10/18 20:17:50  stolpmann
 * 	New feature: dot notation to access contents of dialog
 * variables
 *
 * Revision 3.5  2002/04/10 21:27:52  stolpmann
 * 	New scheme for automatically generated interactor IDs. There
 * are now two parts: <base>_<seq> where <base> is the base number
 * (currently derived from the system clock), and where <seq> is the
 * sequence number. Sequence numbers are no longer reset to 0 after
 * "handle". (The reason for the new ID scheme are buggy browsers that
 * try to remember old form values. It is now very unlikely that a
 * name for a form field is reused before the page expires.)
 *
 * Revision 3.4  2002/04/10 21:08:33  stolpmann
 * 	Fix
 *
 * Revision 3.3  2002/04/10 20:08:31  stolpmann
 * 	There is now some protection against incompatible changes of
 * the format of the session records. Previously, such changes could
 * even crash WDialog processes, because the sessions are serialized by
 * the Marshal module. Now a hash key for the version (both WDialog
 * and OCaml) is prepended to the serialized session string.
 *
 * Revision 3.2  2002/02/14 16:15:21  stolpmann
 * 	Added copyright notice.
 *
 * Revision 3.1  2002/02/12 20:28:59  stolpmann
 * 	Initial release at sourceforge.
 *
 * Revision 1.13  2002/02/11 11:14:21  gerd
 * 	Fix: It is now possible to change the dialog of sessions (required
 * for the Change_dialog exception)
 *
 * Revision 1.12  2002/02/06 00:14:00  gerd
 * 	Updates to ocamlnet-0.92
 *
 * Revision 1.11  2002/02/05 18:44:16  gerd
 * 	New: class instant_session_manager
 *
 * Revision 1.10  2002/01/14 15:03:24  gerd
 * 	Major change: Typing has been completely revised, and almost
 * every tiny thing has now a new type. Also renamed a lot.
 *
 * Revision 1.9  2000/12/21 15:00:37  gerd
 * 	Server popups: The generated "open" function has now a second
 * parameter which is passed back to the application. The Popup_request
 * event gets this parameter. The parameter can be used freely by the
 * application.
 *
 * Revision 1.8  2000/12/04 18:28:02  gerd
 * 	Update
 *
 * Revision 1.7  2000/12/04 12:00:26  gerd
 * 	dump_interactors
 *
 * Revision 1.6  2000/11/30 18:41:49  gerd
 * 	See uiobject.mli rev 1.5
 *
 * Revision 1.5  2000/07/03 12:28:10  gerd
 * 	Improved debugging support: The interactor structure
 * is included as descriprive text on demand.
 *
 * Revision 1.4  2000/05/15 11:46:33  gerd
 * 	Necessary changes for uidebugger.
 *
 * Revision 1.3  2000/05/08 15:31:26  gerd
 * 	New methods: exn_object_declaration and exn_default_context.
 *
 * Revision 1.2  2000/04/17 10:10:52  gerd
 * 	New Cgi module.
 * 	File upload strongly improved.
 *
 * Revision 1.1  2000/04/13 17:42:58  gerd
 * 	Initial revision.
 *
 *
 *)

This web site is published by Informatikbüro Gerd Stolpmann
Powered by Caml