(*
* <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.
*
*
*)