Plasma GitLab Archive
Projects Blog Knowledge

(* $Id: webdav_client_methods.mli 9 2015-01-12 20:00:52Z gerd $ *)

(** Extension of the Http_client module for submitting WebDAV requests.
   See RFC 4918 (http://www.ietf.org/rfc/rfc4918.txt)

   This definition is incomplete.

   Strings are encoded in UTF-8, except where something else is specified.
   URLs are ASCII.

   This module allows it to create method objects that can be passed
   to HTTP pipelines, e.g.

   {[
     let http = new Http_client.pipeline
     
     let propfind_cl = new propfind "http://server/"

     http # add (propfind_cl :> Http_client.http_call);
     http # run();

     (* now look at propfind_cl for response *)
   ]}

   Note that the method objects need to be coerced to [http_call] before
   being passed to the pipeline. There is no way of coercing them back,
   so users should keep the object [propfind] with the original type.
 *)

(* Missing methods: LOCK, UNLOCK *)
(* Missing headers: If *)


open Webdav_http
open Webdav_compat

(** {2 Success codes} *)

(** WebDAV error reporting is complicated: File system conditions are 
    mapped to the HTTP response status codes, which is looks rather
    weird. Also, many operations can return multistatus responses,
    with detailed status codes for each affected URL or even each
    property.
 *)

type call_status =
    [ Http_client.status
    | `Multi_status
    ]
  (** Condensed status information of a WebDAV call:
    * - [`Unserved]: The call has not yet been finished
    * - [`HTTP_protocol_error e]: An error on HTTP level occurred. Corresponds
    *   to the exception {!Http_client.Http_protocol}.
    * - [`Successful]: The call is successful, and the response code is between
    *   200 and 299 (except 207 which is special, see below).
    * - [`Redirection]: The call is successful, and the response code is
    *   between 300 and 399.
    * - [`Client_error]: The call failed with a response code between 400 and
    *   499.
    * - [`Server_error]: The call failed for any other reason.
    * - [`Multi_status]: The server responded with a 207 code. For
    *   [PROPFIND] and [PROPPATCH] the multistatus response includes
    *   detailed data about properties. For other methods the multistatus
    *   response means that for some URL the operation ran into an error
    *   but for other URLs it was successful. The classes defined below
    *   have methods to further analyze the situation (especially [good_urls]
    *   and [bad_urls]).
   *)

val string_of_call_status : call_status -> string

(** {2 General types and values} *)

type property =
    Webdav_xml.property
  (** A property is an XML tree. See {!Webdav_xml.property} for more 
      information
   *)

type prepost_code =
    Webdav_xml.prepost_code
  (** A precondition/postcondition element. Works in the same way
      as [property].
   *)


(** Information about individual properties *)
class type propstat_t =
object
  method properties : property list
    (** The properties this object is about *)

  method status : webdav_status
  method status_code : int
  method status_text : string
  method status_protocol : string
    (** These four methods reflect the status indicated for the property. *)

  method error : prepost_code list
    (** Additional error token *)

  method responsedescription : string
    (** User information. Empty if not found in the response *)

end


(** Information about the response status per path *)
class type response_t =
object
  method href : string list
    (** The URLs this response refers to. The list is non-empty.
        For propfind responses the length is always 1. URLs should be
        ASCII only.
     *)
  method paths : string list
    (** The paths corresponding to the URLs in [href] *)

  method status : webdav_status
  method status_code : int
  method status_text : string
  method status_protocol : string
    (** These four methods reflect the status indicated for the URL.
        If no status is found (as in responses for PROPFIND), the
        status is reported as "200 OK".
     *)

  method propstat : propstat_t list
    (** The properties requested via PROPFIND *)

  method prop_creationdate : float option
  method prop_displayname : string option
  method prop_getcontentlanguage : string option
  method prop_getcontentlength : int64 option
  method prop_getcontenttype : string option
  method prop_getcontenttype_decoded : (string * (string * string) list) option
  method prop_getetag : string option
  method prop_getetag_decoded : Nethttp.etag option
  method prop_getlastmodified : float option
  method prop_resourcetype_is_collection : bool option
    (** Returns the value of the standard property if set. For some
        properties there are versions returning raw and decoded values.
     *)

  method find_prop : string -> property * propstat_t
    (** Return the named property and the propstat data, or raise [Not_found].
        The name is the qualified XML name after prefix normalization
        (e.g. "DAV:creationdate").
     *)

  method error : prepost_code list
    (** Additional error tokens *)

  method responsedescription : string
    (** User information. Empty if not found in the response *)

  method location : string option
    (** The destination URL for redirects (status = 3xx). Should be ASCII *)
end


(** A WebDAV call is a request with response *)
class type webdav_call_t =
object
  inherit Http_client.http_call

  method response_webdav_status : webdav_status
    (** Extended status information for WebDAV purposes *)

  method call_status : call_status
    (** Condensed status information about the call *)

  method multistatus : response_t list
    (** If the [call_status] is [`Multi_status], this list contains
        for every URL the requested data
     *)

  method query_path : string
    (** Returns the target path of this call *)

  method effective_query_path : string
    (** Returns the target path of this call, after following redirections *)

  method paths : string list
    (** Return the paths for which we got multistatus responses. This list
        is empty if the [call_status] is not [`Multi_status].
     *)

  method good_paths : string list
    (** The subset of [paths] for which the response is considered as
        successful. The exeact criterion depends on the type of call.

        For multistatus responses [good_paths <> []] means that there
        was success for at least one path.
     *)

  method bad_paths : string list
    (** The subset of [paths] for which the response is considered as
        erroneous. The exeact criterion depends on the type of call.

        For multistatus responses [bad_paths = []] means that all paths
        could be processed successfully.
     *)

  method fully_successful : bool
    (** Same as
 [call_status = `Successful || (call_status = `Multi_status && bad_paths = [])] 
      *)

  method response_of_path : string -> response_t 
    (** Return the response object for this path. Raises [Not_found] if
        nothing is known about the path (or no multistatus response was
        available).

        In a PROPFIND response the status is per property, not per path.
        So [(response_of_path p) # status] is always [`Ok]. The only
        property that can always be retrieved is [resourcetype], so to find
        out whether a path is generally accessible one should ask for
        that property, and check whether [prop_resourcetype_is_collection]
        is [Some true] (for collections) or [Some false] (for non-collections).
        A value of [None] means that there is an error with the path.

        For other query types the status is per path.

	When the client had to follow redirections, only information about
	the redirected path is available. Use [effective_query_path] to
	determine this path.
     *)

  method responsedescription : string
    (** The overall description *)

  method status_report : string
    (** Returns a client-generated message describing the success of the
        operation URL by URL
     *)

end


(** {2 URL construction} *)


val url_append : string -> string -> string
  (** [url_append base path]: Appends the [path] to the URL [base], and
      returns the new URL. Here, [base] must be a properly encoded URL,
      with [http://server] prefix. [path] is a normal slash-separated
      path without special escaping. The return value is again an
      absolute URL.

      The [path] must be absolute (begins with "/").

      Examples: 
      - [url_append "http://server" "/dir/file" = "http://server/dir/file"]
      - [url_append "http://server/dir" "/file" = "http://server/dir/file"]
      - [url_append "http://server" "/a file" = "http://server/a%20file"]
   *)

(** {2 PROPFIND} *)

type propfind_request =
  [ `Prop of property list
  | `Propname
  | `Allprop of property list
  ]
  (** What is requested:
      - [`Prop l]: Request the properties listed in [l] (which must
        be non-empty). The nodes in [l] are XML elements without children.
      - [`Propname]: Request a list of property names
      - [`Allprop l]: Request all properties, and include especially
        the extension properties [l] (which may be empty). In [l] one
        can request additional properties of the WebDAV namespace that
        are not defined in RFC 4918.
   *)

val propname_creationdate : property
val propname_displayname : property
val propname_getcontentlanguage : property
val propname_getcontentlength : property
val propname_getcontenttype : property
val propname_getetag : property
val propname_getlastmodified : property
val propname_resourcetype : property
  (** Standard WebDAV properties. These objects can be included in
      [`Prop] requests. These properties should be returned anyway
      for [`Allprop []].
   *)

class type propfind_call_t = webdav_call_t

class propfind_call : propfind_call_t
  (** Creates a request object for PROPFIND. Header and body are uninitialized
   *)

class propfind : 
        ?depth:depth -> 
        ?propfind_request:propfind_request ->
        ?strip_prefix:string ->
           string -> propfind_call_t
  (** [new propfind url]: Creates a PROPFIND request. If successful,
      this request is usually responded by a [`Multistatus] document
      listing the status for every matching URL.

      - [depth]: The [Depth] header value: [`Zero] means to only report about
        the object [url], [`One] means to also include data about the direct
        children, and [`Infinity] means to include data of the whole subtree.
        If missing, servers usually assume [`Infinity].
      - [propfind_request]: The detailed request. If missing, servers usually
        assume [`Allprop []].
      - [strip_prefix]: If set this path prefix is stripped from the
        reported paths. Must be an absolute path.
   *)


(** {b Simplified interface for getting file listings} *)

type list_request =
    [ `Existence | `Standard ]
  (** [`Existence] means a list request where only the [resourcetype]
      property is queried - useful for finding out which files exist.
      [`Standard] includes all standard properties.
   *)

class type filelist_t = propfind_call_t
  (** In order to get property values, look up the per-URL response with
      [response_of_url], and then call one of the [prop_*] methods, e.g.

      {[
      let url = List.hd l#list_good_url
      let r = l#response_of_url url
      let t = r#prop_creationdate
      ]}
   *)

class filelist : ?depth:depth -> ?strip_prefix:string -> 
                   list_request -> string -> filelist_t
  (** [let l = new filelist req url]: Creates a new request object for
      retrieving file listings.
   *)


(** {2 PROPPATCH} *)

type proppatch_instruction =
    [ `Remove of property list
    | `Set of property list
    ]
   (** Which properties to modify:
       - [`Remove props]: Removes the properties [props]. The list [props]
         should only contain the property names with empty contents
       - [`Set props]: Sets the properties [props]. The list [props]
         must include properties that include contents. One can create
         such properties with the [encode_*] functions in {!Webdav_xml}.
    *)

type proppatch_request =
    proppatch_instruction list
   (** The property updates are executed in order. The updates are either
       all executed, or none is executed
    *)

class type proppatch_call_t = webdav_call_t

class proppatch_call : propfind_call_t
  (** Creates a request object for PROPPATCH. Header and body are uninitialized
   *)

class proppatch : 
        ?strip_prefix:string -> 
        proppatch_request:proppatch_request ->
        string -> propfind_call_t
  (** [new proppatch ~proppatch_request url]: Creates a PROPPATCH request. 
      If successful,
      this request is usually responded by a [`Multistatus] document
      listing the status for every matching URL.
   *)

(** {2 MKCOL} *)

(** MKCOL creates collections (directories) *)

class type mkcol_call_t = webdav_call_t

class mkcol_call : mkcol_call_t
  (** Creates a request object for MKCOL. Header and body are uninitialized
   *)

class mkcol :
       ?strip_prefix:string ->  string -> mkcol_call_t
  (** [new mkcol uri]: Creates a new collection at [uri].

      On success, the expected status value is [`Created]. See RFC 4918
      for error codes - it is quite interesting.
   *)

(** {2 GET} *)

(** GET requests a document *)

class type get_call_t = webdav_call_t

class get_call : get_call_t
  (** Creates a request object for GET. Header and body are uninitialized
   *)

class get :
        ?strip_prefix:string -> string -> get_call_t
  (** [new get uri]: Gets the document at [uri].

      On success, the expected status value is [`Ok]. Access [response_body]
      to get the downloaded document. The status value [`No_content] is
      also possible.
   *)

(** {2 DELETE} *)

(** DELETE collections and documents *)

class type delete_call_t = webdav_call_t

class delete_call : delete_call_t
  (** Creates a request object for DELETE. Header and body are uninitialized
   *)

class delete :
        ?strip_prefix:string -> string -> delete_call_t
  (** [new delete uri]: Deletes the document or collection at [uri].
      Deletions are always recursive (the whole tree is deleted).

      On success, the expected status value is [`No_content]. When some URLs
      of the tree to delete result in errors, the status value will
      be [`Multi_status] reporting the errors. In that case the delete
      was only incompletely performed. 
   *)

(** {2 PUT} *)

(** Add/replace documents *)

class type put_call_t = webdav_call_t

class put_call : put_call_t
  (** Creates a request object for PUT. Header and body are uninitialized
   *)

class put :
        ?content_type:string ->
        ?content_length:int64 ->
        ?expect_handshake:bool ->
        ?strip_prefix:string ->
        string -> Netmime.mime_body -> put_call_t
  (** [new put url body]: Uploads the contents of [body] to the server so
      it will be visible at [url].

      - [content_type]: The media type of the body. The server is free
        to ignore this.
      - [content_length]: The length of the body, if already known.
        There are servers that accept PUT only if the content length
        is set in the header.
      - [expect_handshake]: Set this to [true] to enable a special
        handshake before the body is uploaded. This is reasonable
        when the PUT request may cause errors - the error can be received
        before the upload starts. There might be compatibility problems,
        though, as this feature was incorrectly specified in some versions
        of HTTP.

      Note that some servers do not permit that existing files are replaced.
      The RFC says nothing about this case, though.
   *)

(** {2 COPY} *)

(** Copy documents, collections, or subtrees *)

class type copy_call_t = webdav_call_t

class copy_call : copy_call_t
  (** Creates a request object for COPY. Header and body are uninitialized
   *)

class copy :
        ?depth:depth ->
        ?overwrite:bool ->
        ?strip_prefix:string ->
        string -> string -> copy_call_t
  (** [new copy src_url dest_url]: Copies the object(s) at [src_url]
      so that they become visible at [dest_url]. This works for documents,
      collections, or whole subtrees.

      The [dest_url] needs to be the full URL, including the 
      [http://server] prefix.

      - [depth]: If set to [`Zero], only the single object at [src_url]
        is copied without member objects (in case of collections).
        If set to [`Infinity], the whole subtree is copied.
        The depth value of [`One] is illegal. If missing servers usually
        assume [`Infinity].
      - [overwrite]: Whether it is allowed to overwrite destination
        objects. If missing servers usually assume [true].
   *)

(** {2 MOVE} *)

(** Move documents, collections, or subtrees *)

class type move_call_t = webdav_call_t

class move_call : copy_call_t
  (** Creates a request object for MOVE. Header and body are uninitialized
   *)

class move :
        ?overwrite:bool ->
        ?strip_prefix:string ->
        string -> string -> move_call_t
  (** [new move src_url dest_url]: Moves the object(s) at [src_url]
      so that they become visible at [dest_url]. This works for documents,
      and whole subtrees (moves of collections are always recursive).

      The [dest_url] needs to be the full URL, including the 
      [http://server] prefix.

      - [overwrite]: Whether it is allowed to overwrite destination
        objects. If missing servers usually assume [true].
   *)


(**/**)

(* Backward compat: *)

class type list_t = filelist_t

class list : ?depth:depth -> ?strip_prefix:string -> 
             list_request -> string -> list_t
  (* other name for list_t. [list] is a bad name because the type ['a list]
     exists already
   *)

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