Plasma GitLab Archive
Projects Blog Knowledge

(* netcgi_compat.ml

   Copyright (C) 2005-2006

     Christophe Troestler
     email: Christophe.Troestler@umh.ac.be
     WWW: http://math.umh.ac.be/an/

   This library is free software; see the file LICENSE for more information.

   This library 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 file
   LICENSE for more details.
*)
(* $Id: netcgi_compat.ml,v 1.12 2005/11/04 00:52:05 chris_77 Exp $ *)

exception Not_implemented of string

module Netcgi_env =
struct
  type input_mode = [ `Standard (* | `Direct *) ]

  type input_state =
      [ `Start
      | `Receiving_header | `Received_header
      | `Receiving_body | `Received_body      ]

  type output_mode = [ `Standard (* | `Direct *) ]

  type output_state =
      [ `Start
      | `Sending_header      | `Sent_header
      | `Sending_body        | `Sent_body
      | `Sending_part_header | `Sent_part_header
      | `Sending_part_body   | `Sent_part_body
      | `End
      ]

  type protocol_version = Nethttp.protocol_version
  type protocol_attribute = Nethttp.protocol_attribute
  type protocol = Nethttp.protocol
  type workaround =
      [ `Work_around_MSIE_Content_type_bug | `Work_around_backslash_bug  ]

  type cgi_config = {
    tmp_directory : string;
    tmp_prefix : string;
    permitted_http_methods : string list;
    permitted_input_content_types : string list;
    input_content_length_limit : int;
    workarounds : workaround list;
  }

  let default_config =
    let default_tmp_directory = Netsys_tmp.tmp_directory() in
    {
      tmp_directory = default_tmp_directory;
      tmp_prefix = "netcgi";
      permitted_http_methods = ["GET"; "HEAD"; "POST"];
      permitted_input_content_types = [ "multipart/form-data";
					"application/x-www-form-urlencoded" ];
      input_content_length_limit = max_int;
      workarounds = [ `Work_around_MSIE_Content_type_bug;
		      `Work_around_backslash_bug ]
    }

  let meth_of_string = function
    | "GET" -> `GET
    | "HEAD" -> `HEAD
    | "POST" -> `POST
    | "DELETE" -> `DELETE
    | "PUT" -> `PUT
    | m -> raise(Not_implemented ("Netcgi_env.of_compat_config: HTTP method name not convertible: " ^ m))


  let string_of_meth = function
    | `GET -> "GET"
    | `HEAD -> "HEAD"
    | `POST -> "POST"
    | `DELETE -> "DELETE"
    | `PUT -> "PUT"


  let of_compat_config c = {
    Netcgi.tmp_directory = c.tmp_directory;
    Netcgi.tmp_prefix = c.tmp_prefix;
    Netcgi.permitted_http_methods =
      List.map meth_of_string c.permitted_http_methods;

    Netcgi.permitted_input_content_types = c.permitted_input_content_types;

    Netcgi.input_content_length_limit = c.input_content_length_limit;
    Netcgi.max_arguments = 10000;

    Netcgi.workarounds =
      List.map (function
		| `Work_around_MSIE_Content_type_bug -> `MSIE_Content_type_bug
		| `Work_around_backslash_bug -> `Backslash_bug
	       ) c.workarounds;

    Netcgi.default_exn_handler = false
  }


  let to_compat_config c = {
    tmp_directory = c.Netcgi.tmp_directory;
    tmp_prefix = c.Netcgi.tmp_prefix;
    permitted_http_methods =
      List.map string_of_meth c.Netcgi.permitted_http_methods;

    permitted_input_content_types = c.Netcgi.permitted_input_content_types;

    input_content_length_limit = c.Netcgi.input_content_length_limit;

    workarounds =
      List.map (function
		| `MSIE_Content_type_bug -> `Work_around_MSIE_Content_type_bug
		| `Backslash_bug -> `Work_around_backslash_bug
		| ( `Work_around_MSIE_Content_type_bug
		  | `Work_around_backslash_bug ) as x -> x
	       ) c.Netcgi.workarounds
  }


  class type cgi_environment =
  object
    method config : cgi_config
    method cgi_gateway_interface  : string
    method cgi_server_software    : string
    method cgi_server_name        : string
    method cgi_server_protocol    : string
    method cgi_server_port        : int option
    method cgi_request_method     : string
    method cgi_path_info          : string
    method cgi_path_translated    : string
    method cgi_script_name        : string
    method cgi_query_string       : string
    method cgi_remote_host        : string
    method cgi_remote_addr        : string
    method cgi_auth_type          : string
    method cgi_remote_user        : string
    method cgi_remote_ident       : string
    method cgi_property          : ?default:string -> string -> string
    method cgi_properties : (string * string) list
    method cgi_https              : bool
    method cgi_request_uri : string
    method protocol : protocol
    method input_header : Netmime.mime_header
    method input_header_field : ?default:string -> string -> string
    method multiple_input_header_field : string -> string list
    method input_header_fields : (string * string) list
    method user_agent : string
    method cookies : (string * string) list
    method input_content_length : int
    method input_content_type_string : string
    method input_content_type : (string * (string * Netmime_string.s_param) list)
    method input_ch : Netchannels.in_obj_channel
    method input_state : input_state
    method set_input_state : input_state -> unit
    method output_header : Netmime.mime_header
    method output_header_field : ?default:string -> string -> string
    method multiple_output_header_field : string -> string list
    method output_header_fields : (string * string) list
    method set_output_header_field : string -> string -> unit
    method set_multiple_output_header_field : string -> string list -> unit
    method set_output_header_fields : (string * string) list -> unit
    method set_status : Nethttp.http_status -> unit
    method send_output_header : unit -> unit
    method output_ch : Netchannels.out_obj_channel
    method output_state : output_state
    method set_output_state : output_state -> unit
    method log_error : string -> unit
  end


  class to_compat_environment (env:Netcgi.cgi_environment) =
    let config = to_compat_config env#config in
  object
    val env = env

    method config = config

    method cgi_gateway_interface  = env#cgi_gateway_interface
    method cgi_server_software    = env#cgi_server_software
    method cgi_server_name        = env#cgi_server_name
    method cgi_server_protocol    = env#cgi_server_protocol
    method cgi_server_port        = env#cgi_server_port
    method cgi_request_method     = env#cgi_request_method
    method cgi_path_info          = env#cgi_path_info
    method cgi_path_translated    = env#cgi_path_translated
    method cgi_script_name        = env#cgi_script_name
    method cgi_query_string       = env#cgi_query_string
    method cgi_remote_host        = env#cgi_remote_host
    method cgi_remote_addr        = env#cgi_remote_addr
    method cgi_auth_type          = env#cgi_auth_type
    method cgi_remote_user        = env#cgi_remote_user
    method cgi_remote_ident       = env#cgi_remote_ident
    method cgi_property           = env#cgi_property
    method cgi_properties 	  = env#cgi_properties
    method cgi_https              = env#cgi_https
    method cgi_request_uri        = env#cgi_property ~default:"" "REQUEST_URI"
    method protocol = env#protocol

    method input_header = env#input_header

    method input_header_field = env#input_header_field
    method multiple_input_header_field = env#multiple_input_header_field
    method input_header_fields = env#input_header_fields
    method user_agent = env#user_agent
    method cookies =
      List.map (fun c -> (Netcgi.Cookie.name c, Netcgi.Cookie.value c))
	env#cookies
    method input_content_length = env#input_content_length
    method input_content_type_string = env#input_content_type_string
    method input_content_type = env#input_content_type()

    method input_ch =
      (raise (Not_implemented "input_ch (of cgi_environment)") 
	 : Netchannels.in_obj_channel)
    method input_state =
      (raise (Not_implemented "input_state (of cgi_environment)")
	 : input_state)
    method set_input_state =
      (raise (Not_implemented "set_input_state (of cgi_environment)")
	 : input_state -> unit)

    method output_header = env#output_header
    method output_header_field = env#output_header_field
    method multiple_output_header_field = env#multiple_output_header_field
    method output_header_fields = env#output_header_fields
    method set_output_header_field = env#set_output_header_field
    method set_multiple_output_header_field =
      env#set_multiple_output_header_field
    method set_output_header_fields = env#set_output_header_fields
    method set_status = env#set_status
    method send_output_header = env#send_output_header

    method output_ch = env#out_channel
    method output_state =
      (raise (Not_implemented "output_state (of cgi_environment)")
	 : output_state)
    method set_output_state =
      (raise (Not_implemented "set_output_state (of cgi_environment)")
	 : output_state -> unit)
    method log_error = env#log_error
  end


  let to_compat_environment =
    new to_compat_environment

  let of_compat_environment (env:cgi_environment) : Netcgi.cgi_environment =
  object(self)
    method config = of_compat_config env#config
    method cgi_gateway_interface  = env#cgi_gateway_interface
    method cgi_server_software    = env#cgi_server_software
    method cgi_server_name        = env#cgi_server_name
    method cgi_server_protocol    = env#cgi_server_protocol
    method cgi_server_port        = env#cgi_server_port
    method cgi_request_method     = env#cgi_request_method
    method cgi_path_info          = env#cgi_path_info
    method cgi_path_translated    = env#cgi_path_translated
    method cgi_script_name        = env#cgi_script_name
    method cgi_query_string       = env#cgi_query_string
    method cgi_remote_host        = env#cgi_remote_host
    method cgi_remote_addr        = env#cgi_remote_addr
    method cgi_auth_type          = env#cgi_auth_type
    method cgi_remote_user        = env#cgi_remote_user
    method cgi_remote_ident       = env#cgi_remote_ident
    method cgi_property           = env#cgi_property
    method cgi_properties 	  = env#cgi_properties
    method cgi_https              = env#cgi_https
    method protocol               = env#protocol

    method input_header           = env#input_header
    method input_header_field     = env#input_header_field
    method multiple_input_header_field = env#multiple_input_header_field
    method input_header_fields    = env#input_header_fields
    method user_agent             = env#user_agent
    method input_content_length   = env#input_content_length
    method input_content_type_string = env#input_content_type_string
    method input_content_type()   = env#input_content_type

    method output_header          = env#output_header
    method output_header_field    = env#output_header_field
    method multiple_output_header_field = env#multiple_output_header_field
    method output_header_fields   = env#output_header_fields
    method set_output_header_field = env#set_output_header_field
    method set_multiple_output_header_field =
                                    env#set_multiple_output_header_field
    method set_output_header_fields = env#set_output_header_fields
    method set_status             = env#set_status

    val mutable header_sent = false
    method send_output_header()   =
      if not header_sent then (
	env#send_output_header();
	header_sent <- true
      )
    method output_ch              = env#output_ch
    method out_channel            = env#output_ch

    method log_error              = env#log_error

    method cookies =
      List.map
	(fun (n,v) -> Netcgi_common.Cookie.make n v)
	env#cookies

    method cookie n = 
      List.find (fun c -> Netcgi_common.Cookie.name c = n) self#cookies
  end
end



module Netcgi_types =
struct
  class type simple_message = Netmime.mime_body

  type store = [ `Memory | `File of string ]
  type representation =
      [ `Simple of simple_message | `MIME of Netmime.mime_message ]

  class type cgi_argument =
  object
    method name : string
    method value : string
    method open_value_rd : unit -> Netchannels.in_obj_channel
    method ro : bool
    method store : store
    method content_type : string
    method content_type_params : (string * Netmime_string.s_param) list
    method charset : string
    method filename : string option
    method representation : representation
    method finalize : unit -> unit
    method set_value : string -> unit
    method open_value_wr : unit -> Netchannels.out_obj_channel
  end


  let to_compat_argument (arg:Netcgi.cgi_argument) =
  object
    val arg = arg

    method name = arg#name
    method value = arg#value
    method open_value_rd = arg#open_value_rd
    method ro = true
    method store = arg#store
    method content_type = fst(arg#content_type())
    method content_type_params = snd(arg#content_type())
    method charset = arg#charset
    method filename = arg#filename
    method representation = arg#representation
    method finalize = arg#finalize
    method set_value =
      (raise(Netmime.Immutable "Netcgi_types.cgi_argument"): string -> unit)
    method open_value_wr =
      (raise(Netmime.Immutable "Netcgi_types.cgi_argument"):
	 unit -> Netchannels.out_obj_channel)
  end


  let of_compat_argument (arg:cgi_argument) : Netcgi.cgi_argument =
  object
    val arg = arg
    method name = arg#name
    method value = arg#value
    method open_value_rd = arg#open_value_rd
    method store = arg#store
    method content_type () = (arg#content_type, arg#content_type_params)
    method charset = arg#charset
    method filename = arg#filename
    method representation = arg#representation
    method finalize = arg#finalize
  end

  type cgi_cookie = Nethttp.netscape_cookie = {
    cookie_name : string;
    cookie_value : string;
    cookie_expires : float option;
    cookie_domain : string option;
    cookie_path : string option;
    cookie_secure : bool;
  }

  type status = Nethttp.http_status

  type request_method = [ `GET | `HEAD | `POST | `DELETE
  | `PUT of cgi_argument ]

  type cache_control = [ `No_cache | `Max_age of int | `Unspecified ]

  type query_string_spec =
      [ `Initial | `Current | `Args of cgi_argument list | `None ]

  type other_url_spec = [ `Env | `This of string | `None ]


  class type cgi_activation =
  object
    method environment : Netcgi_env.cgi_environment
    method request_method : request_method
    method initial_arguments : (string * cgi_argument) list
    method initial_argument : string -> cgi_argument
    method initial_argument_value : ?default:string -> string -> string
    method initial_multiple_argument : string -> cgi_argument list
    method arguments : (string * cgi_argument) list
    method argument : string -> cgi_argument
    method argument_value : ?default:string -> string -> string
    method multiple_argument : string -> cgi_argument list
    method set_arguments : ?fin:bool -> cgi_argument list -> unit
    method update_argument : ?fin:bool -> cgi_argument -> unit
    method update_multiple_argument : ?fin:bool -> cgi_argument list -> unit
    method delete_argument : ?fin:bool -> string -> unit
    method url :
      ?protocol:Netcgi_env.protocol ->
      ?with_authority:other_url_spec ->
      ?with_script_name:other_url_spec ->
      ?with_path_info:other_url_spec ->
      ?with_query_string:query_string_spec ->
      unit -> string
    method output : Netchannels.trans_out_obj_channel
    method set_header :
      ?status:status ->
      ?content_type:string ->
      ?cache:cache_control ->
      ?filename:string ->
      ?language:string ->
      ?script_type:string ->
      ?style_type:string ->
      ?set_cookie:cgi_cookie list ->
      ?fields:(string * string list) list ->
      unit -> unit
    method set_redirection_header : string -> unit
    method finalize : unit -> unit
  end


  class to_compat_activation (cgi:Netcgi.cgi) =
  object
    val env = Netcgi_env.to_compat_environment cgi#environment
    val args = List.map to_compat_argument cgi#arguments
      (* FIXME: the curr_args should duplicate the body and header of
	 initial ones -- need to duplicate the files... :( *)
    val mutable curr_args =
      List.map to_compat_argument cgi#arguments
    val cgi = cgi

    method environment = env
    method request_method : request_method =
      match cgi#request_method with
      | `GET | `HEAD | `POST | `DELETE as m -> m
      | `PUT cgi -> `PUT(to_compat_argument cgi)

    method initial_arguments = List.map (fun a -> (a#name, a)) args
    method initial_argument name =
      List.find (fun a -> a#name = name) args
    method initial_argument_value ?default name =
      try (List.find (fun a -> a#name = name) args)#value
      with Not_found -> (match default with
			 | None -> raise Not_found
			 | Some d -> d)
    method initial_multiple_argument name =
      List.filter (fun a -> a#name = name) args


    method arguments = List.map (fun a -> (a#name, a)) curr_args
    method argument name =
      List.find (fun a -> a#name = name) curr_args
    method argument_value ?default name =
      try (List.find (fun a -> a#name = name) curr_args)#value
      with Not_found -> (match default with
			 | None -> raise Not_found
			 | Some d -> d)
    method multiple_argument name =
      List.filter (fun a -> a#name = name) curr_args

    method set_arguments ?(fin=true) new_args =
      if fin then
	List.iter (fun a -> if not(List.mem a new_args) then a#finalize())
	  curr_args;
      curr_args <- new_args

    method update_argument ?(fin=true) new_arg =
      let name = new_arg#name in
      let keep a =
	if fin && a#name = name && a <> new_arg then (a#finalize(); false)
	else a#name <> name in
      curr_args <- new_arg :: (List.filter keep curr_args)

    (* All arguments in [arglist] must have the same name. *)
    method update_multiple_argument ?(fin=true) arglist =
      match arglist with
      | [] -> ()
      | a0 :: tl ->
	  let name = a0#name in
	  if List.exists (fun a -> a#name <> name) tl then
	    invalid_arg "update_multiple_argument";
	  let keep a =
	    if fin && a#name = name && not(List.mem a arglist) then
	      (a#finalize(); false)
	    else a#name <> name in
	  curr_args <- arglist @ List.filter keep curr_args

    method delete_argument ?(fin=true) name =
      let keep a =
	if fin && a#name = name then (a#finalize(); false)
	else a#name <> name in
      curr_args <- List.filter keep curr_args


    method url ?protocol ?with_authority ?with_script_name ?with_path_info
      ?(with_query_string=(`None: query_string_spec)) () =
      cgi#url ?protocol ?with_authority ?with_script_name
	?with_path_info
	~with_query_string:(
	  match with_query_string with
	  | `Initial -> `Env
	  | `Current ->   `This(List.map of_compat_argument curr_args)
	  | `Args args -> `This(List.map of_compat_argument args)
	  | `None -> `None)
	()


    method output = cgi#out_channel

    method set_header ?status ?content_type ?cache ?filename
      ?language ?script_type ?style_type ?(set_cookie=[]) ?fields () =
      let now = Unix.time() in
      let make_cookie c =
	Netcgi.Cookie.make
	  ?domain:c.cookie_domain
	  ?max_age:(match c.cookie_expires with
		    | None -> None
		    | Some t -> Some(truncate(t -. now)))
	  ?path:c.cookie_path
	  ~secure:c.cookie_secure
	  c.cookie_name c.cookie_value in
      cgi#set_header ?status ?content_type
	~set_cookies:(List.map make_cookie set_cookie)
	?cache ?filename ?language ?script_type ?style_type ?fields ()

    method set_redirection_header loc =
      cgi#set_redirection_header loc


    method finalize () =
      List.iter (fun a -> a#finalize()) args;
      List.iter (fun a -> a#finalize()) curr_args;
      cgi#finalize()
  end


  let to_compat_activation = new to_compat_activation


  let of_compat_activation (cgi_act:cgi_activation) =
    let env = Netcgi_env.of_compat_environment(cgi_act#environment) in
  object(self)
    (* We have no idea of the output_type of [cgi_act] so we
       initialize with [`Direct] and override [out_channel]. *)
    inherit Netcgi_common.cgi env (`Direct "")
      (match cgi_act#request_method with
       | `GET | `HEAD | `POST | `DELETE as m -> m
       | `PUT a -> `PUT(of_compat_argument a))
      (List.map (fun (_, a) -> of_compat_argument a) cgi_act#initial_arguments)

    (* Override methods that depend on the unknown output_type *)

    method out_channel = cgi_act#output

    method set_header ?status ?content_type ?content_length
      ?(set_cookie=[]) ?(set_cookies=[])
      ?cache ?filename ?language ?script_type ?style_type ?(fields=[])
      () =
      let now = Unix.time() in
      let old_cookie c =
	{ cookie_name = Netcgi_common.Cookie.name c;
	  cookie_value = Netcgi_common.Cookie.value c;
	  cookie_expires = (match Netcgi_common.Cookie.max_age c with
			    | None -> None
			    | Some t -> Some(float t +. now));
	  cookie_domain = Netcgi_common.Cookie.domain c;
	  cookie_path = Netcgi_common.Cookie.path c;
	  cookie_secure = Netcgi_common.Cookie.secure c;
	} in
      let fields = 
	match content_length with
	  | None -> fields
	  | Some size -> 
	      ("content-length", [string_of_int size]) ::
		(List.filter
		   (fun (n,_) -> String.lowercase n <> "content-length")
		   fields) in
      (* The old [set_header] knows whether the output is
	 transactional or not. *)
      cgi_act#set_header ?status ?content_type ?cache ?filename
	?language ?script_type ?style_type ~fields
	~set_cookie:(set_cookie @ (List.map old_cookie set_cookies))
	()

    method set_redirection_header ?set_cookies ?(fields=[]) loc =
      (* There is no way of getting the old set_redirection_header to
	 accept to set other fields.  Thus, use [set_header].  *)
      self#set_header ?set_cookies
	~fields:(fields @ [("Location", [loc])] )
	()
  end
end


module Netcgi =
struct
  type argument_processing =
      [ `Memory
      | `File
      | `Automatic ]
	
  type operating_type =
      [ `Direct of string (* separator *)
      | `Transactional of
          (Netcgi_env.cgi_config -> Netchannels.out_obj_channel ->
             Netchannels.trans_out_obj_channel)
      ]

  let buffered_transactional_optype =
    `Transactional (fun config ch -> new Netchannels.buffered_trans_channel ch)


  let tempfile_transactional_optype =
    `Transactional
      (fun config ch ->
	 let tmp_directory = config.Netcgi_env.tmp_directory in
	 let tmp_prefix = config.Netcgi_env.tmp_prefix in
	 new Netchannels.tempfile_trans_channel ~tmp_directory ~tmp_prefix ch
      )

  class simple_argument ?(ro=true) name value =
  object
    val super = new Netcgi_common.simple_arg name value

    method name = super#name
    method value = super#value
    method open_value_rd = super#open_value_rd
    method ro = super#ro
    method store = super#store
    method content_type = fst(super#content_type())
    method content_type_params = snd(super#content_type())
    method charset = super#charset
    method filename = super#filename
    method representation = super#representation
    method finalize = super#finalize
    method set_value = super#set_value
    method open_value_wr = super#open_value_wr
  end

  class mime_argument ?work_around_backslash_bug name mime =
  object
    val super =
      new Netcgi_common.mime_arg ?work_around_backslash_bug ~name mime

    method name = super#name
    method value = super#value
    method open_value_rd = super#open_value_rd
    method ro = super#ro
    method store = super#store
    method content_type = fst(super#content_type())
    method content_type_params = snd(super#content_type())
    method charset = super#charset
    method filename = super#filename
    method representation = super#representation
    method finalize = super#finalize
    method set_value = super#set_value
    method open_value_wr = super#open_value_wr
  end

  let split_name_val s =  (* Same as in Netcgi_cgi *)
    try
      let i = String.index s '=' in
      (String.sub s 0 i, String.sub s (i+1) (String.length s - i - 1))
    with Not_found ->
      (s, "")

  class std_activation 
           ?env ?(processing = 
	            fun _ _ -> `Automatic) 
           ?(operating_type = ( `Direct "" : operating_type ) ) () =
    let (new_env, in_obj) =
      match env with
	| None ->
	    if Netcgi_cgi.is_cgi() then
	      (* Following is stolen from Netcgi_cgi: *)
	      let (properties, input_header) =
		Array.fold_left
		  (fun l e -> 
		     Netcgi_common.update_props_inheader (split_name_val e) l)
		  ([], []) (Unix.environment()) in
	      let in_obj = new Netchannels.input_channel stdin in
	      let out_obj = new Netchannels.output_channel stdout in
	      let new_env = 
		new Netcgi_common.cgi_environment 
		      ~config:Netcgi.default_config
		      ~properties ~input_header out_obj in
	      (new_env, in_obj)
	    else
	      raise(Not_implemented "class std_activation: The environment is not CGI")
		(* [cgi] enters test mode in this case *)

	| Some e -> 
	    if e # input_state <> `Received_header then
	      failwith "Netcgi.std_activation: environment indicates the wrong input state";
	    if e # output_state <> `Start then
	      failwith "Netcgi.std_activation: environment indicates the wrong output state";
	    (Netcgi_env.of_compat_environment e,
	     e # input_ch) in

    let new_arg_store n_env arg_name n_arg_hdr = 
      let arg_hdr = new Netmime.basic_mime_header n_arg_hdr#fields in
      let p = processing arg_name arg_hdr in
      (p : argument_processing :> Netcgi_common.arg_store_type) in

    let new_output_type =
      match operating_type with
	| `Direct s -> `Direct s 
	| `Transactional f ->
	    `Transactional
	      (fun n_config out_obj ->
		 let o_config = Netcgi_env.to_compat_config n_config in
		 f o_config out_obj
	      ) in

    let new_cgi =
      Netcgi_common.cgi_with_args 
	(new Netcgi_common.cgi) 
	new_env new_output_type in_obj new_arg_store in

    let () =
      match env with
	| None -> ()
	| Some e ->
	    e # set_input_state `Received_body in

    Netcgi_types.to_compat_activation new_cgi

end

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