(* $Id$ *)
(** WebDAV client (synchronous) *)
(** This module defines a simple synchronous WebDAV client.
For async support, users should take the method classes in
{!Webdav_client_methods} and push them onto {!Http_client}
pipelines.
*)
open Webdav_client_methods
open Webdav_http
open Webdav_compat
(** General remarks:
{b Paths.}
The paths passed to the access methods must be absolute
(start with "/"), and must not include the [http://] prefix.
All unsafe characters in the paths (like ";" or "?") are
automatically percent-encoded.
{b Synchronous operation.}
The access methods return an object that already includes
the servers response. All TCP connections are already closed.
{b Depth.}
The depth says whether the operation is only applied to the
passed object ([`Zero]), to the object and the direct children
([`One]), or to the whole subtree ([`Infinity]). The latter is
the default if the depth is not specified.
{b Fixup.}
All methods have a [fixup] argument. The passed function is called
back with the call object before the WebDAV request is submitted to
the server. This can be used to set options in the call object.
{b Error checking.}
The returned call object has methods reporting the exact error
condition. For most applications it is sufficient to only
check whether [fully_successful] is true. A number of methods
can return partial successes, though, e.g. a recursive operation
could only be carried out for a number of files. If this needs
to be analyzed by the caller, one should first check [call_status]
and if [`Multi_status] is returned, the methods [good_paths] and
[bad_paths] list for which paths the operation was successful
or not successful, respectively.
*)
class type webdav_client_t =
object
method base_url : string
(** The base URL this client class prepends to all URLs. For example,
if the base URL is ["http://localhost/foo"] and the operation
[propfind "/bar"] is invoked, actually the target URL is
set to the concatation of both, namely ["http://localhost/foo/bar"].
The base URL is encoded, if necessary (percent encoding).
*)
method pipeline : Http_client.pipeline
(** The backing pipeline *)
method propfind : ?depth:depth ->
?propfind_request:propfind_request ->
?fixup:(Http_client.http_call -> unit) ->
string -> propfind_call_t
(** [propfind path]: Submits a [PROPFIND] request to the server.
The returned [propfind_call_t] object already includes the
response.
*)
method list : ?depth:depth ->
?fixup:(Http_client.http_call -> unit) ->
list_request -> string -> list_t
(** [list req path]: Submits a special [PROPFIND] request for
getting a file listing.
Example: Get file listing at [/foo]:
{[
let l = client # list `Existence "/foo" in
if not l#fully_successful then failwith "Not successful";
let paths = l#good_paths
]}
*)
method proppatch : ?fixup:(Http_client.http_call -> unit) ->
proppatch_request:proppatch_request ->
string -> proppatch_call_t
(** [proppatch ~proppatch_request path]: Submits a [PROPPATCH]
request to change properties. The change is described
in [proppatch_request].
Example: Set the [Content-Type]
{[
let proppatch_request =
[ `Set [ Webdav_xml.encode_getcontenttype ("text/plain",[]) ] ] in
client # proppatch ~proppatch_request "/file"
]}
*)
method mkcol : ?fixup:(Http_client.http_call -> unit) ->
string -> mkcol_call_t
(** [mkcol path]: Submits an [MKCOL] request (create a new
collection/directory).
*)
method delete : ?fixup:(Http_client.http_call -> unit) ->
string -> delete_call_t
(** [delete path]: Submits a [DELETE] request. Deletes are always
recursive. Servers are allowed to delete as much as possible
(instead of an "all or nothing" semantics), and to return
partial success only.
*)
method get : ?store:Http_client.response_body_storage ->
?fixup:(Http_client.http_call -> unit) ->
string -> get_call_t
(** [get path]: Submits a [GET] request (download a file).
The downloaded body can be accessed with the [response_body]
method of the returned object.
The [store] argument specifies where the body is stored.
Example download (in-memory):
{[
let g = client # get "/path"
let s = g # response_body # value
]}
Example download to file:
{[
let store = `File (fun () -> "/home/gerd/downloaded_file")
let g = client # get ~store "/path"
let ch = g # response_body # open_value_rd()
ch # input_line() (* get first line *)
]}
*)
method put : ?content_type:string ->
?content_length:int64 ->
?expect_handshake:bool ->
?fixup:(Http_client.http_call -> unit) ->
string -> Netmime.mime_body -> put_call_t
(** [put path body]: Submits a [PUT] request (upload a file).
The [body] includes the data to upload.
- [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. (See the function [length_of_body] below
for getting the length.)
- [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.
Example upload:
{[
let b = new Netmime.file_mime_body "/home/gerd/large.bin"
let n = length_of_body b
let p = client # put ~content_length:n ~expect_handshake:true
"/path" b
if not p#fully_successful then
failwith "Error"
]}
*)
method copy : ?depth:depth ->
?overwrite:bool ->
?dest_base_url:string ->
?fixup:(Http_client.http_call -> unit) ->
string -> string -> copy_call_t
(** [copy src_path dest_path]: Submits a [COPY] request. By default,
it is not possible to overwrite files during the copy. One has
to set [overwrite:true] to allow this.
Note that [dest_path] is interpreted as "copy onto", not "copy into"
if it is a directory, i.e. the [src_path] subtree will be available
also as [dest_path] if the copy is successful. This is different
from the semantics of the [cp] command.
In a recursive copy, servers are free to copy only as many of the
files as possible, i.e. partial success is possible.
If [dest_base_url] is set, one can specify to copy to a different
WebDAV server. This is rarely supported by servers, though.
*)
method move : ?overwrite:bool ->
?dest_base_url:string ->
?fixup:(Http_client.http_call -> unit) ->
string -> string -> move_call_t
(** [move src_path dest_path]: Submits a [MOVE] request. By default,
it is not possible to overwrite files during the move. One has
to set [overwrite:true] to allow this.
The [MOVE] method is always recursive.
Note that [dest_path] is interpreted as "move onto", not "move into"
if it is a directory, i.e. the [src_path] subtree will be available
as [dest_path] if the move is successful. This is different
from the semantics of the [mv] command.
Servers are free to move only as many of the
files as possible, i.e. partial success is possible.
If [dest_base_url] is set, one can specify to move to a different
WebDAV server. This is rarely supported by servers, though.
*)
end
class webdav_client : ?pipeline : Http_client.pipeline ->
string ->
webdav_client_t
(** [new webdav_client base_url]: Creates a new WebDAV client with this
base URL
Example: [new webdav_client "http://server:8080/webdavroot"]
*)
val webdav_client : ?pipeline : Http_client.pipeline ->
string ->
webdav_client_t
(** Same as function *)
val url_path : string -> string
(** Returns the decoded path of an encoded URL *)
val length_of_body : Netmime.mime_body -> int64
(** Determine the length of a body *)