(* $Id$ *)

(** Senders and receivers for the FTP data connection *)


(* *********************************************************************)
(** {1 Types and Exceptions} *)


type ftp_data_prot =
  [ `C | `S | `E | `P ]
  (** See {!Netftp_client.ftp_data_prot} *)

type ftp_protector =
    { ftp_wrap_limit : unit -> int;
      ftp_wrap_s : string -> string;
      ftp_wrap_m : Netsys_types.memory -> Netsys_types.memory -> int;
      ftp_unwrap_s : string -> string;
      ftp_unwrap_m : Netsys_types.memory -> Netsys_types.memory -> int;
      ftp_prot_level : ftp_data_prot;
      ftp_close : unit -> unit;
      mutable ftp_gssapi_props : Netsys_gssapi.client_props option;
      mutable ftp_auth_loop : bool;
    }
  (** The functions for encrypting (wrapping) and decrypting (unwrapping)
      messages when an RFC 2228 security layer is active.
   *)


(** An [out_record_channel] can be used to output files with record
 * structure. This is purely abstract, as Unix does not support such
 * files natively, so this kind of channel is usually mapped to a flat
 * representation when stored in a real file, e.g. record boundaries 
 * become newline characters.
 *)
class type out_record_channel =
object
  inherit Netchannels.out_obj_channel
  method output_eor : unit -> unit
    (** Finishes the current record. 
     *
     * The record model is as follows: At the beginning of the channel,
     * or after [output_eor], a new record can be potentially started.
     * However, the record counts only as existing when either at least
     * one byte is output, or the record is explicitly ended with a
     * [output_eor] call.
     *
     * This rule allows it to represent channels containing no records.
     * There is an ambiguity at the end of the channel, however: It is
     * possible that there are characters between the last EOR and the
     * EOF. This could also be represented by printing another EOR just
     * before EOF. The preferred representation is the latter.
     *
     * This model correlates to the usual rules for line-structured
     * files, so think EOR = EOL.
     *)
end

(** An [in_record_channel] can be used to read files with record
 * structure. This is purely abstract, as Unix does not support such
 * files natively, so this kind of channel is usually mapped to a flat
 * representation when stored in a real file, e.g. record boundaries 
 * become newline characters.
 *)
class type in_record_channel =
object
  inherit Netchannels.in_obj_channel
  method input_eor : unit -> unit
    (** Skips over the remaining data of the current record and the
     * record boundary to the next record. Raises [End_of_file] when
     * the current record is the "EOF" record (see below for explanations).
     *
     * A record channel can be read as follows: After opening the channel,
     * one can read the contents of the first record with the [input]
     * method. The end of the record is indicated by an [End_of_file]
     * exception. By calling [input_eor], the next record is opened
     * and can be read.
     *
     * After the last real record, there is always a special 
     * "EOF" record which is empty, and must be ignored by applications
     * processing records. This means, after opening an empty channel,
     * the current record is this "EOF" record, and [input_eor] raises
     * [End_of_file]. After reading a non-empty channel, one can
     * do [input_eor] after the last regular record, and the following
     * [input_eor] raises [End_of_file].
     *)
end

type local_receiver =
    [ `File_structure of Netchannels.out_obj_channel
    | `Record_structure of out_record_channel
    ]
    (** The [local_receiver] is the object that gets the data received
     * over the data connection.
     *
     * Page structure is not supported.
     *)

type local_sender =
    [ `File_structure of Netchannels.in_obj_channel
    | `Record_structure of in_record_channel
    ]
    (** The [local_sender] is the object that provides the data sent
     * over the data connection.
     *
     * Page structure is not supported.
     *)

type transmission_mode =
    [ `Stream_mode
    | `Block_mode
    ]
    (** The transmission mode as described in RFC 959. 
     * Compressed mode is not supported.
     *)


type descr_state =
    [ `Clean
    | `Transfer_in_progress
    | `Down
    ]
    (** Describes the state of the socket used for data transfers.
     * The state [`Clean] means that a new data transfer may be started,
     * either because the socket is new, or the last block transfer was
     * properly finished. The state [`Transfer_in_progress] means that
     * data is being transferred, but also that the transfer is aborted.
     * The state [`Down] means that the socket is already at least half-closed,
     * i.e. EOF was sent in one direction.
     *)

type text_data_repr =
    [ `ASCII      of Netconversion.encoding
    | `ASCII_unix of Netconversion.encoding
    | `EBCDIC     of Netconversion.encoding
    ]
    (** Possible representation of text data:
     * [`ASCII] means an ASCII-compatible encoding where the newline
     * character is represented by CR/LF. [`ASCII_unix] is the same
     * but newline is only LF. [`EBCDIC] is an EBCDIC variant.
     *
     * The argument specifies the exact variant to be used, e.g.
     * [`ASCII `Enc_iso88591] or [`EBCDIC `Enc_cp1047].
     *
     * It is illegal to use [`ASCII] or [`ASCII_unix] with an ASCII-
     * incompatible encoding, as well as combining  [`EBCDIC] with a
     * non-EBCDIC encoding. Wrong conversions would be the result of
     * this.
     *)

(* *********************************************************************)
(** {1 Data Stream Converters} *)


class write_out_record_channel : 
        repr:text_data_repr -> 
	Netchannels.out_obj_channel -> 
	  out_record_channel
  (** Provides an [out_record_channel] that represents EOR as 
   * newline character.
   *
   * @param repr Determines the newline character to use
   *)


class read_in_record_channel :
        repr:text_data_repr ->
	Netchannels.in_obj_channel ->
	  in_record_channel
  (** Provides an [in_record_channel] that takes newline characters as
   * EOR representation.
   *
   * In this implementation, [input_line] can be used to read the full
   * contents of a record (=line). However, [input_line] does not
   * switch to the next record.
   *
   * @param repr Determines the newline character to use
   *)



class data_converter : 
        fromrepr:text_data_repr -> torepr:text_data_repr -> 
	  Netchannels.io_obj_channel
  (** Creates a data conversion pipe converting [fromrepr] to
   * [torepr].
   *
   * For simplicity, in an [`ASCII] input stream the CR characters are
   * discarded, and the LF characters are taken as newline characters.
   * In an output [`ASCII] stream, the CR/LF sequence is correctly
   * created for newline characters.
   *)


(* *********************************************************************)
(** {1 Engines} *)


(** The common type of FTP data engines *)
class type ftp_data_engine =
object
  inherit [unit] Uq_engines.engine
  method descr : Unix.file_descr
    (** The socket to use for data transfers. This class never closes
     * this socket, but it may be shut down at least partially.
     *)

  method descr_state : descr_state
    (** The socket state *)
end    


(** This engine receives data on a FTP data connection, and forwards
 * them to a local receiver. The socket must already be open.
 *
 * It is ensured that [local_receiver] is always closed after operation
 * (whether successful or not). The socket [descr] remains open.
 *
 * [tls]: may be set to [(config, peer_name, resume_data)]
 *)
class ftp_data_receiver : 
        ?tls:((module Netsys_crypto_types.TLS_CONFIG) *
              string option *
              string option) ->
        ?protector:ftp_protector ->
        esys:Unixqueue.event_system ->
	mode:transmission_mode ->
	local_receiver:local_receiver ->
	descr:Unix.file_descr ->
        timeout:float ->
        timeout_exn:exn ->
	unit ->
object
  inherit ftp_data_engine

  method local_receiver : local_receiver
    (** The local receiver. It is closed when the logical EOF is found in the
     * data connection
     *)

end


(** This engine sends data over a FTP data connection coming from
 * a local sender. The socket must already be open.
 *
 * It is ensured that [local_sender] is always closed after operation
 * (whether successful or not). The socket [descr] remains open.
 *
 * [tls]: may be set to [(config, peer_name, resume_data)]
 *)
class ftp_data_sender :
        ?tls:((module Netsys_crypto_types.TLS_CONFIG) *
              string option *
              string option) ->
        ?protector:ftp_protector ->
        esys:Unixqueue.event_system ->
	mode:transmission_mode ->
	local_sender:local_sender ->
	descr:Unix.file_descr ->
        timeout:float ->
        timeout_exn:exn ->
	unit ->
object
  inherit ftp_data_engine

  method local_sender : local_sender
    (** The local sender. It is closed after usage.
     *)
end
