(* $Id: nethttpd_types.mli 2195 2015-01-01 12:23:39Z gerd $
 *
 *)

(*
 * Copyright 2005 Baretta s.r.l. and Gerd Stolpmann
 *
 * This file is part of Nethttpd.
 *
 * Nethttpd 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.
 *
 * Nethttpd 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
 *)

(** Type definitions for the HTTP daemon
  *
  * {b Contents}
  *
  * - {!Nethttpd_types.exceptions}
  * - {!Nethttpd_types.environment}
  * - {!Nethttpd_types.service}
  * - {!Nethttpd_types.helpers}
 *)

(** Many types can also be found in the [Nethttp] module (part of netstring).
  * Furthermore, [Netcgi_env] and [Netcgi_types] are of interest (part of cgi).
 *)

(** {1:exceptions Exceptions} *)

open Nethttp

exception Standard_response of http_status * http_header option * string option
  (** Some HTTP containers allow you to raise this exception. The standard
    * response corresponding to [http_status] is sent back to the client.
    * If the third argument exists, an entry into the error log
    * is written.
   *)

(**********************************************************************)

(** {1:environment Environment} *)

type output_state =
    [ `Start
    | `Sending
    | `End
    ]

val string_of_output_state : output_state -> string
  (** Debugging *)

(** An extension of [cgi_environment] for use with the daemon. The methods
  * retrieving the socket addresses are virtual.
 *)
class type virtual v_extended_environment =
object
  inherit Netcgi.cgi_environment

  method virtual server_socket_addr : Unix.sockaddr
  method virtual remote_socket_addr : Unix.sockaddr
    (** These are always the physical IP addresses and ports of the two endpoints
      * of the current connection.
     *)

  method cgi_request_uri : string
    (** The full request URI. Identical to the CGI property "REQUEST_URI" *)

  method log_props : (string * string) list -> unit
    (** Remember this version of [cgi_properties] as the one sent to the
        [config_log_access] function
     *)

  method input_channel : Netchannels.in_obj_channel
    (** The input channel for reading the body of the request *)

  method input_body_size : int64
    (** so far known, or 0L *)

  method request_body_rejected : bool
    (** so far known, or false *)

  method send_file : Unix.file_descr -> int64 -> unit
    (** Sends the output header with a file as body. The file must already be open,
     * and positioned where the transmission begins. The number is the length
     * of the transmission.
     *
     * This method may return immediately when it is possible to open the file, and
     * to set the kernel up for file transmission. Otherwise a [Unix_error] is
     * raised. It is also allowed that this method blocks until the file is actually
     * transmitted.
     *
     * It is not allowed to print to the output channel and to call [send_file].
     * Only one transmission method must be invoked.
     *)

  method virtual output_state : output_state ref
    (** Reflects the state of the output generation:
        - [`Start]: Nothing is generated yet
        - [`Sending]: Output is already being sent
        - [`End]: The response (for a single request) has been fully sent
     *)

  method tls_session_props : Nettls_support.tls_session_props option
    (** The TLS session properties if TLS is active *)

end



(** Same as [v_extended_environment], but no virtual methods 
 *)
class type extended_environment =
object
  inherit v_extended_environment
  method server_socket_addr : Unix.sockaddr
  method remote_socket_addr : Unix.sockaddr
  method output_state : output_state ref
end


(** {2 Construction of environments} *)

class virtual empty_environment :
object
  inherit v_extended_environment
  val mutable config : Netcgi.config
  val mutable protocol : Nethttp.protocol
  val mutable in_header : http_header
  val mutable out_header : http_header
  val mutable properties : (string * string) list
  val mutable in_channel : Netchannels.in_obj_channel
  val mutable out_channel : Netchannels.out_obj_channel
  val mutable tls_session_props : Nettls_support.tls_session_props option
end
  (** This class implements an environment with defined internal containers.
    * These containers are empty, but fully functional.
    * The following methods are empty and should be redefined:
    * - [send_output_header]
    * - [send_file]
    * - [log_error]
    * - [log_props]
    *
    * The virtual methods, of course, must be defined!
   *)

class redirected_environment : 
        ?in_header : http_header ->
        ?properties : (string * string) list ->
        ?in_channel : Netchannels.in_obj_channel ->
        extended_environment ->
          extended_environment
  (** This class overlays the input-side containers of an existing environment.
    * The output-side containers ([out_header], and [out_channel])
    * are physically identical with the existing environment.
    *
    * If one of the argument is not passed on class instantiation, the corresponding
    * overlay container is initialized with the current value of the passed
    * environment. As exception of this rule, the input channel is initialized with
    * an empty input channel.
   *)

(** {2 Auxiliary Functions for Environments} *)

val output_static_response : #extended_environment ->
                             http_status -> http_header option -> string -> unit
  (** Outputs the string argument as response body, together with the given status and
    * the header (optional). Response header fields are set as follows:
    * - The [Content-Length] is set to the length of the string.
    * - The [Content-Type] is set to "text/html" unless given by the header.
    *
    * If the header is not passed, the header of the environment is taken. If the header
    * argument exists, however, it overrides the header of the environment.
   *)

val output_file_response : #extended_environment ->
                           http_status -> http_header option ->
                           string -> int64 -> int64 -> unit
  (** Outputs the contents of a file as response body, together with the given status and
    * the header (optional). The string is the file name. The first int64 number is
    * the position in the file where to start, and the second number is the length
    * of the body.  Response header fields are set as follows:
    * - The [Content-Length] is set to the length of the string.
    * - The [Content-Type] is set to "text/html" unless given by the header.
    *
    * Note that [Content-Range] is not set automatically, even if the file is only
    * partially transferred.
    *
    * If the header is not passed, the header of the environment is taken. If the header
    * argument exists, however, it overrides the header of the environment.
    *
    * The function raises [Sys_error] when the file cannot be read.
   *)

(** {1 Generating error responses, logging} *)

class type request_info =
object
  method server_socket_addr : Unix.sockaddr
    (** The socket address of this server. May raise [Not_found] if
        there is no such address *)
  method remote_socket_addr : Unix.sockaddr
    (** The socket address of the client. May raise [Not_found] if
        there is no such address *)
  method request_method : string
    (** The method like [GET]. May raise [Not_found] *)
  method request_uri : string
    (** The URI of the client request. This is often without the
        server designation, i.e. just [/path?query].
        May raise [Not_found] *)
  method input_header : Nethttp.http_header
    (** The request header. May raise [Not_found] *)
  method cgi_properties : (string * string) list
    (** The distilled CGI properties *)
  method input_body_size : int64
    (** The size of the input body. May raise [Not_found] *)
  method tls_session_props : Nettls_support.tls_session_props option
    (** The TLS properties *)
end


class type full_info =
object
  inherit request_info
  method response_status_code : int
    (** The HTTP status code to response *)
  method request_body_rejected : bool
    (** Whether the request body was rejected *)
  method output_header : Nethttp.http_header
    (** The response header *)
  method output_body_size : int64
    (** The size of the output body. *)
end


class create_full_info : response_status_code:int ->
                         request_body_rejected:bool ->
                         output_header:Nethttp.http_header ->
                         output_body_size:int64 ->
                         request_info -> full_info
  (** Creates a [full_info] object by adding to a [request_info] object *)


class type error_response_params =
object
  inherit request_info

  method response_status_code : int
    (** The HTTP status code to response *)

  method error_message : string
    (** The error message explaining the detail that went wrong *)
end


class type min_config =
object
  method config_error_response : error_response_params -> string
  method config_log_error : request_info -> string -> unit
end
  (** Minimal configuration needed for [output_std_response] *)

val output_std_response : #min_config -> #extended_environment ->
                          http_status -> http_header option -> string option -> unit
  (** Outputs a "standard response" for the [http_status]. The string argument
    * is an optional entry into the error log.
    *
    * If the header is not passed, an empty header is taken. If the header argument
    * exists, this header is taken. The header of the environment is never taken.
   *)


(**********************************************************************)

(** {1:service Service Providers}
  *
  * Service providers are defined using the three class types:
  * - [http_service]: The service provider as such. When a HTTP header has been
  *   received, and the service provider is invoked, it returns an object
  *   fitting to the next class type, [http_service_receiver]. This object
  *   is tagged with [`Accept_body]; at this point there are also alternate ways
  *   of processing, see below.
  * - [http_service_receiver]: The task of this object is to receive the request
  *   body. When the body has been completely received, the object is notified,
  *   and returns a third object of type [http_service_generator].
  * - [http_service_generator]: The task of this object is to generate the
  *   response.
  *
  * An implementor is free to define only one class that satisfies all three
  * class types at once. However, this is only an option.
  *
  * The three objects reflect three stages of HTTP processing. The stages have
  * made explicit to allow the implementor of services to intercept the points
  * in time when the processing of the next stage begins. Furthermore, in multi-threaded
  * environments it is allowed that the stages are performed in the contexts of
  * different threads.
  *
  * In addition to the three-stage model there also several faster paths of 
  * processing:
  * - [`Reject_body] can be used to refuse the acceptance of the request body when
  *   it is already clear that an error response is sent back. This path skips
  *   the stage [http_service_receiver].
  * - [`Static] can be used to send a constant string back (only to be used
  *   when the string needs not to be computed)
  * - [`File] can be used to send the contents of a file back (only to be used
  *   when the file already exists)
 *)

exception Redirect_request of string * http_header
  (** The "early" redirect is only allowed in stage 1 of HTTP processing.
    * The string argument is the new URI path of the request. The header can also
    * be exchanged except the fields that are needed to decode the request
    * body. It is not possible to change the method.
   *)

exception Redirect_response of string * http_header
  (** The "late" redirect is only allowed in stage 3 of HTTP processing.
    * The string argument is the new URI path of the request.  The header can also
    * be exchanged except the fields that are needed to decode the request
    * body. {b The method is always changed to [GET].}
   *)


class type http_service_generator =
object
  method generate_response : extended_environment -> unit
    (** Third stage of HTTP processing:
      * This method is called when the HTTP request has been completely received,
      * and the response is going to be generated. This method can again be called
      * from a different thread than the previous stages. It is allowed to spend
      * any necessary time to compute the response.
      *
      * When the method returns, the request processing is finished. No more data
      * is allowed to be written to the output channel past this moment.
      *
      * The method may raise [Standard_response] to generate one of the
      * standard messages.
     *)
end


class type http_service_receiver =
object
  method process_body : extended_environment -> http_service_generator
    (** Second stage of HTTP processing:
      * This method is called when the body is expected to be arriving. Note that
      * the main difference to [process_header] is that this method can be
      * called from a different thread. It is allowed (and expected) that this method
      * blocks while reading the input. Of course, the input and output
      * channels of the environment are unlocked, and can be used.
      * 
      * When the method returns, the request processing continues with stage 3.
      * Any body data not read is dropped.
      *
      * It is allowed that this method generates a response (or part of it),
      * although this should be better done in stage 3.
      *
      * The method may raise [Standard_response] to generate one of the
      * standard messages.
      *
      * One way of implementing this method is to instantiate [Netcgi.std_activation].
     *)

end


type http_service_reaction =
    [ `Accept_body of http_service_receiver
    | `Reject_body of http_service_generator
    | `Static of http_status * http_header option * string
    | `File of http_status * http_header option * string * int64 * int64
    | `Std_response of http_status * http_header option * string option
    ]
    (** Indicates the immediate reaction upon an arriving HTTP header:
      * - [`Accept_body] is the regular way of processing requests. If necessary,
      *   the client is told to continue sending the rest of the request.
      * - [`Reject_body] can be used when the body of the request is not needed,
      *   and the response will be negative.
      * - [`Static] means to send the header and a constant string back as response.
      *   The header is taken from the environment if not explicitly passed,
      *   Note: The [Content-Length] header is automatically added. The [Content-Type]
      *   defaults to "text/html".
      * - [`File] is similar to this, but the data come from a file. The file
      *   is specified by an absolute pathname. The range of the file is given
      *   by the start position and the length of the range.
      *   The header is taken from the environment if not explicitly passed,
      *   Note: The [Content-Length] header is automatically added. The [Content-Type]
      *   defaults to "text/html".
      * - [`Std_response] is similar to [`Static], however the body is the standard
      *   text for the status code. If the header is omitted, it is taken as empty.
      *   The third argument is an optional entry into the error log.
      *   Note: The [Content-Length] header is automatically added. The [Content-Type]
      *   defaults to "text/html".
     *)

class type ['a] http_service =
object
  method name : string
    (** The name of the type of the service provider *)

  method def_term :'a
    (** The definition term *)

  method print : Format.formatter -> unit
    (** Outputs the definition term to a formatter *)

  method process_header : extended_environment -> http_service_reaction
    (** First stage of HTTP processing:
      * This method is called when the HTTP header has been received. This method
      * must return quickly without blocking the thread how to go on. For example,
      * this could look as follows:
      * - Check whether the client is allowed to access this resource. If this
      *   can be done immediately, it should be done now. (If an external service
      *   must be queried, the check must not be done now, but deferred to the
      *   second or third stage.) If the access is denied, an error response can
      *   be sent back using [`Static], [`File], or, if computed, using [`Reject_body].
      * - Check whether the request is delegated to another service provider
      *   (e.g. lookup by hostname, by port number, or by URI). In this case,
      *   the result of this [process_header] call is simply the result of the
      *   [process_header] call of the other service provider.
      * - If this service provider generates the contents, there are mainly two
      *   types of reaction. If the contents are stored in a file, one can simply
      *   return [`File]. Otherwise, return [`Accept_body] to continue with the
      *   second stage. Note that even if no body is expected to arrive, one must
      *   go through the second stage, and drop any unexpected body.
      *
      * The argument of this function is the Netcgi environment. The header is
      * complete, including the request method. One cannot access the input and
      * output channels at this stage of processing.
     *)
end

(**********************************************************************)

(** {1:helpers Helpers} *)

val update_alist : ('a * 'b) list -> ('a * 'b) list -> ('a * 'b) list
  (** [update_alist updl l]: Returns the alist with all elements of [updl]
    * and all elements of [l] that are not member of [updl].
   *)
