(* $Id: webdav_client_methods.ml 9 2015-01-12 20:00:52Z gerd $ *) open Webdav_xml open Webdav_http open Webdav_compat open Printf type call_status = [ Http_client.status | `Multi_status ] type property = Webdav_xml.property type prepost_code = Webdav_xml.prepost_code class type propstat_t = Webdav_xml.propstat_t class type response_t = Webdav_xml.response_t class type webdav_call_t = object inherit Http_client.http_call method response_webdav_status : webdav_status method call_status : call_status method multistatus : response_t list method query_path : string method effective_query_path : string method paths : string list method good_paths : string list method bad_paths : string list method fully_successful : bool method response_of_path : string -> response_t method responsedescription : string method status_report : string end class type propfind_call_t = webdav_call_t class type list_t = webdav_call_t class type filelist_t = webdav_call_t class type mkcol_call_t = webdav_call_t class type get_call_t = webdav_call_t class type delete_call_t = webdav_call_t class type put_call_t = webdav_call_t class type copy_call_t = webdav_call_t class type move_call_t = webdav_call_t type propfind_request = [ `Prop of property list | `Propname | `Allprop of property list ] type list_request = [ `Existence | `Standard ] type proppatch_instruction = Webdav_xml.proppatch_instruction type proppatch_request = Webdav_xml.proppatch_request let status_of_code code = if code >= 200 && code <= 299 then `Successful else if code >= 300 && code <= 399 then `Redirection else if code >= 400 && code <= 499 then `Client_error else `Server_error let string_of_call_status (st:call_status) = match st with | `Unserved -> "Unserved" | `Http_protocol_error e -> "Http_protocol_error(" ^ Printexc.to_string e ^ ")" | `Successful -> "Successful" | `Redirection -> "Redirection" | `Client_error -> "Client_error" | `Server_error -> "Server_error" | `Multi_status -> "Multi_status" let name_of_prop p = p#localname (* CHECK, maybe we want to know more *) let allowed_xml_types = [ "application/xml"; "text/xml" ] let rm_trailing_slash p = let q = List.rev p in match q with | [] -> [""] | [""] -> [""] | "" :: q' -> List.rev q' | _ -> p let url_append base_url path = let b = Neturl.parse_url base_url in let p1 = Neturl.split_path path in if p1=[] || List.hd p1 <> "" then failwith "Webdav_client_method.url_append: path is not absolute"; let p2 = Neturl.norm_path p1 in if List.mem "." p2 || List.mem ".." p2 then failwith "Webdav_client_method.url_append: bad use of . or .. in path"; Neturl.string_of_url (Neturl.modify_url ~path:(rm_trailing_slash(Neturl.url_path b) @ List.tl p2) b) class virtual webdav_call_mixin = object(self) (* from Http_client.http_call: *) method virtual response_status_code : int method virtual response_status_text : string method virtual response_header : Netmime.mime_header method virtual response_body : Netmime.mime_body method virtual request_uri : string method virtual effective_request_uri : string method virtual status : Http_client.status val mutable multistatus_lz = lazy (assert false) val mutable paths = [] val mutable good_paths = [] val mutable bad_paths = [] val mutable response_tbl = Hashtbl.create 1 val mutable strip_prefix_p = None initializer ( multistatus_lz <- lazy (self # real_multistatus) ) method response_webdav_status = let code = self#response_status_code in webdav_status_of_int code method call_status = match self#status with | `Successful -> let code = self#response_status_code in if code = 207 then `Multi_status else `Successful | st -> (st :> call_status) method query_path = url_path ?strip_prefix:strip_prefix_p self#request_uri method effective_query_path = url_path ?strip_prefix:strip_prefix_p self#effective_request_uri method private response_is_good (r : response_t) = (* may be overridden in subclass *) let rcode = r#status_code in rcode >= 200 && rcode <= 299 method multistatus = match self#response_webdav_status with | `Multi_status -> (Lazy.force multistatus_lz) # responses | _ -> [] method responsedescription = match self#response_webdav_status with | `Multi_status -> (Lazy.force multistatus_lz) # responsedescription | _ -> "" method private real_multistatus = let hd = self # response_header in let content_type = try hd # field "Content-type" with Not_found -> "" in let content_type_dec,_ = get_content_type hd in if not (List.mem content_type_dec allowed_xml_types) then failwith "WebDAV client error: multistatus response is not XML"; let ms = Webdav_xml.parse_multistatus_body (* ~namespace_manager:XXX *) ?strip_prefix:strip_prefix_p ~content_type (self # response_body # open_value_rd()) in let tbl = Hashtbl.create 42 in List.iter (fun r -> List.iter (fun p -> Hashtbl.replace tbl p r) r#paths ) ms#responses; let plist = List.sort String.compare (Hashtbl.fold (fun p _ acc -> p::acc) tbl []) in paths <- plist; response_tbl <- tbl; let good, bad = List.partition (fun p -> let r = Hashtbl.find tbl p in self # response_is_good r ) plist in good_paths <- good; bad_paths <- bad; ms method paths = ignore(self#multistatus); paths method good_paths = ignore(self#multistatus); good_paths method bad_paths = ignore(self#multistatus); bad_paths method response_of_path p = ignore(self#multistatus); Hashtbl.find response_tbl p method fully_successful = match self#call_status with | `Successful -> true | `Multi_status -> self#bad_paths = [] | _ -> false method status_report = let b = Buffer.create 1001 in let indent = " " in let add_line url msg = bprintf b "%s:\n%s%s\n" url indent msg in let base_url = self # effective_request_uri in let status_class code = if code >= 200 && code <= 299 then "Success" else if code >= 300 && code <= 399 then "Redirection" else if code >= 400 && code <= 499 then "Client-side error" else if code >= 500 && code <= 599 then "Server-side error" else "Unknown type" in ( match self#status with | `Unserved -> add_line base_url "Unserved" | `Http_protocol_error e -> add_line base_url ("Exception " ^ Printexc.to_string e) | `Redirection | `Client_error | `Server_error -> add_line base_url (sprintf "%s (%d %s)" (status_class self#response_status_code) self#response_status_code self#response_status_text) | `Successful -> if self#response_status_code = 207 then ( let rlist = self # multistatus in List.iter (fun r -> List.iter (fun u -> bprintf b "%s:\n" u) r#href; let plist = r # propstat in if plist = [] then bprintf b "%s%s (%d %s)\n" indent (status_class r#status_code) r#status_code r#status_text else ( (* Sort props by status_code *) let props_of_code = Hashtbl.create 42 in let text_of_code = Hashtbl.create 42 in List.iter (fun p -> let code = p#status_code in let text = p#status_text in let old_props = try Hashtbl.find props_of_code code with Not_found -> [] in let new_props = List.map name_of_prop p#properties in Hashtbl.replace props_of_code code (new_props @ old_props); Hashtbl.replace text_of_code code text ) plist; let codes = Hashtbl.fold (fun k v acc -> k :: acc) props_of_code [] in let codes' = List.sort Pervasives.compare codes in List.iter (fun code -> let props = Hashtbl.find props_of_code code in bprintf b "%s%s properties (%d %s)\n" indent (status_class code) code (Hashtbl.find text_of_code code); List.iter (fun prop -> bprintf b "%s%s%s\n" indent indent prop ) props ) codes' ) ) rlist ) else add_line base_url (sprintf "Success (%d %s)" self#response_status_code self#response_status_text) ); Buffer.contents b end let propname_creationdate = Webdav_xml.propname_creationdate let propname_displayname = Webdav_xml.propname_displayname let propname_getcontentlanguage = Webdav_xml.propname_getcontentlanguage let propname_getcontentlength = Webdav_xml.propname_getcontentlength let propname_getcontenttype = Webdav_xml.propname_getcontenttype let propname_getetag = Webdav_xml.propname_getetag let propname_getlastmodified = Webdav_xml.propname_getlastmodified let propname_resourcetype = Webdav_xml.propname_resourcetype class propfind_call = object(self) inherit Http_client.generic_call inherit webdav_call_mixin method private fixup_request() = () method private def_request_method = "PROPFIND" method private def_is_idempotent = true method private def_has_req_body = true method private def_has_resp_body = true method private def_empty_path_replacement = "/" end class propfind ?depth ?propfind_request ?strip_prefix url = object(self) inherit propfind_call val mutable ref_body = (new Netmime.memory_mime_body "", 0) initializer ( self # set_request_uri url; ( match depth with | None -> () | Some d -> Header.set_depth (self # request_header `Base) d ); let b = Buffer.create 507 in ( match propfind_request with | None -> () | Some req -> let ch = new Netchannels.output_buffer b in Webdav_xml.write_propfind_request ch req; ch # close_out(); ); let s = Buffer.contents b in let body = ro_mime_body s in self # set_request_body body; ref_body <- (body, String.length s); strip_prefix_p <- strip_prefix; ) method private fixup_request() = let (p_body, p_len) = ref_body in let body = self # request_body in if body == p_body then ( (* Set the length only when we are sure the user did not mess with the body *) Nethttp.Header.set_content_length (self # request_header `Effective) (Int64.of_int p_len) ) method private response_is_good (r : response_t) = let plist = r#propstat in List.for_all (fun p -> let pcode = p # status_code in pcode >= 200 && pcode <= 299 ) plist end class filelist ?depth ?strip_prefix (lreq : list_request) url = let propfind_request = match lreq with | `Existence -> `Prop [ propname_resourcetype ] | `Standard -> `Allprop [] in object(self) inherit propfind ?depth ~propfind_request ?strip_prefix url method private response_is_good (r : response_t) = r # prop_resourcetype_is_collection <> None end class type proppatch_call_t = webdav_call_t class proppatch_call = object(self) inherit Http_client.generic_call inherit webdav_call_mixin method private fixup_request() = () method private def_request_method = "PROPPATCH" method private def_is_idempotent = true method private def_has_req_body = true method private def_has_resp_body = true method private def_empty_path_replacement = "/" end class proppatch ?strip_prefix ~proppatch_request url = object(self) inherit proppatch_call val mutable ref_body = (new Netmime.memory_mime_body "", 0) initializer ( self # set_request_uri url; let b = Buffer.create 507 in let ch = new Netchannels.output_buffer b in Webdav_xml.write_proppatch_request ch proppatch_request; ch # close_out(); let s = Buffer.contents b in let body = ro_mime_body s in self # set_request_body body; ref_body <- (body, String.length s); strip_prefix_p <- strip_prefix; ) method private fixup_request() = let (p_body, p_len) = ref_body in let body = self # request_body in if body == p_body then ( (* Set the length only when we are sure the user did not mess with the body *) Nethttp.Header.set_content_length (self # request_header `Effective) (Int64.of_int p_len) ) method private response_is_good (r : response_t) = let plist = r#propstat in List.for_all (fun p -> let pcode = p # status_code in pcode >= 200 && pcode <= 299 ) plist end class mkcol_call = object(self) inherit Http_client.generic_call inherit webdav_call_mixin method private fixup_request() = () method private def_request_method = "MKCOL" method private def_is_idempotent = true method private def_has_req_body = true (* it may have *) method private def_has_resp_body = true method private def_empty_path_replacement = "/" end class mkcol ?strip_prefix url = object(self) inherit mkcol_call initializer self # set_request_uri url; strip_prefix_p <- strip_prefix end class get_call = object(self) inherit Http_client.get_call inherit webdav_call_mixin end class get ?strip_prefix url = object(self) inherit get_call initializer self # set_request_uri url; strip_prefix_p <- strip_prefix; end class delete_call = object(self) inherit Http_client.generic_call inherit webdav_call_mixin method private fixup_request() = () method private def_request_method = "DELETE" method private def_is_idempotent = true method private def_has_req_body = true (* it may have in WebDAV ctx *) method private def_has_resp_body = true method private def_empty_path_replacement = "/" end class delete ?strip_prefix url = object(self) inherit delete_call initializer self # set_request_uri url; strip_prefix_p <- strip_prefix; end class put_call = object(self) inherit Http_client.generic_call inherit webdav_call_mixin method private fixup_request() = () method private def_request_method = "PUT" method private def_is_idempotent = true method private def_has_req_body = true method private def_has_resp_body = true method private def_empty_path_replacement = "/" end class put ?content_type ?content_length ?(expect_handshake=false) ?strip_prefix url body = object(self) inherit put_call initializer self # set_request_uri url; self # set_request_body body; ( match content_type with | None -> () | Some ct -> (self # request_header `Base) # update_field "Content-type" ct ); ( match content_length with | None -> () | Some n -> (self # request_header `Base) # update_field "Content-length" (Int64.to_string n) ); if expect_handshake then (self # request_header `Base) # update_field "Expect" "100-continue"; strip_prefix_p <- strip_prefix; end class copy_call = object(self) inherit Http_client.generic_call inherit webdav_call_mixin method private fixup_request() = () method private def_request_method = "COPY" method private def_is_idempotent = true method private def_has_req_body = true method private def_has_resp_body = true method private def_empty_path_replacement = "/" end class copy ?depth ?overwrite ?strip_prefix src_url dest_url = object(self) inherit copy_call initializer self # set_request_uri src_url; let hdr = self # request_header `Base in Header.set_destination hdr dest_url; ( match depth with | None -> () | Some d -> Header.set_depth hdr d ); ( match overwrite with | None -> () | Some b -> Header.set_overwrite hdr b ); strip_prefix_p <- strip_prefix; end class move_call = object(self) inherit Http_client.generic_call inherit webdav_call_mixin method private fixup_request() = () method private def_request_method = "MOVE" method private def_is_idempotent = true method private def_has_req_body = true method private def_has_resp_body = true method private def_empty_path_replacement = "/" end class move ?overwrite ?strip_prefix src_url dest_url = object(self) inherit move_call initializer self # set_request_uri src_url; let hdr = self # request_header `Base in Header.set_destination hdr dest_url; ( match overwrite with | None -> () | Some b -> Header.set_overwrite hdr b ); strip_prefix_p <- strip_prefix; end class list = filelist