Plasma GitLab Archive
Projects Blog Knowledge

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

open Webdav_http
open Webdav_compat
open Pxp_document

type property =
    'node Pxp_document.extension Pxp_document.node as 'node

type prepost_code =
    'node Pxp_document.extension Pxp_document.node as 'node

type known_prepost_code =
    [ `No_external_entities 
    | `Preserved_live_properties
    | `Propfind_finite_depth
    | `Cannot_modify_protected_property
    ]

class type propstat_t =
object
  method properties : property list
  method status : webdav_status
  method status_code : int
  method status_text : string
  method status_protocol : string
  method error : prepost_code list
  method responsedescription : string
end


class type response_t =
object
  method href : string list
  method paths : string list
  method status : webdav_status
  method status_code : int
  method status_text : string
  method status_protocol : string
  method propstat : propstat_t list
  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
  method find_prop : string -> property * propstat_t
  method error : prepost_code list
  method responsedescription : string
  method location : string option
end


class type multistatus_t =
object
  method responses : response_t list
  method responsedescription : string
end


type propfind_request =
  [ `Prop of property list
  | `Propname
  | `Allprop of property list
  ]

type proppatch_instruction =
    [ `Remove of property list
    | `Set of property list
    ]

type proppatch_request =
    proppatch_instruction list


let spec = Pxp_tree_parser.default_namespace_spec


let namespace_manager() =
  let m = Pxp_dtd.create_namespace_manager() in
  m # add_namespace "DAV" "DAV:";
  m


let check_dtd (dtd : Pxp_dtd.dtd) =
  if dtd#encoding <> `Enc_utf8 then
    failwith "Webdav_xml.check_dtd: The character encoding must be UTF-8";

  if not dtd#arbitrary_allowed then
    failwith "Webdav_xml.check_dtd: DTD is not compatible with well-formedness mode";

  if dtd#element_names <> [] then
    failwith "Webdav_xml.check_dtd: Element declarations not allowed";

  if dtd#notation_names <> [] then
    failwith "Webdav_xml.check_dtd: Notation declarations not allowed";

  List.iter
    (fun name ->
       let (ent,_) = dtd#gen_entity name in
       if Pxp_dtd.Entity.get_type ent <> `Internal then
	 failwith "Webdav_xml.check_dtd: Only internal entities are allowed"
    )
    dtd#gen_entity_names;
  List.iter
    (fun name ->
       let ent = dtd#par_entity name in
       if Pxp_dtd.Entity.get_type ent <> `Internal then
	 failwith "Webdav_xml.check_dtd: Only internal entities are allowed"
    )
    dtd#par_entity_names;

  let m = dtd # namespace_manager in

  ( try
      let u = m # get_uri_list "DAV" in
      if u <> ["DAV:"] then raise Not_found;
      let p = m # get_normprefix "DAV:" in
      if p <> "DAV" then raise Not_found
    with
      | _ ->
	  failwith "Webdav_xml.check_dtd: Bad namespace declaration for DAV"
  );

  ()


let dtd() =
  let dtd = Pxp_dtd.create_dtd `Enc_utf8 in
  dtd # allow_arbitrary;
  dtd # set_namespace_manager (namespace_manager());
  dtd


let rec strip_prefix_path1 prefix path =
  match (prefix,path) with
    | [], path ->
	path
    | [""], [] ->
	[]
    | (p0::prefix'), (p1::path') ->
	if p0=p1 then
	  strip_prefix_path1 prefix' path'
	else
	  if p0="" then
	    path
	  else
	    failwith "Webdav_xml: found URL outside the configured prefix"
    | _, [] ->
	failwith "Webdav_xml: found URL outside the configured prefix"


let strip_prefix_path prefix path =
  match prefix with
    | [] -> path
    | "" :: _ -> "" :: strip_prefix_path1 prefix path
    | _ -> failwith "Webdav_xml: bad prefix"
	

let url_path ?strip_prefix u =
  let up =
    Neturl.url_path 
      (Neturl.parse_url
	 ~base_syntax:Neturl.ip_url_syntax
	 ~accept_8bits:true
	 (Neturl.fixup_url_string 
	    u)) in
  let up' =
    match strip_prefix with
      | None -> up
      | Some prefix ->
	  let q = Neturl.split_path prefix in
	  strip_prefix_path q up in
  let p =
    Neturl.join_path up' in
  Netconversion.verify `Enc_utf8 p;
  p



let date_time_re =
  Netstring_str.regexp
    "\\([0-9][0-9][0-9][0-9]\\)-\\([0-9][0-9]\\)-\\([0-9][0-9]\\)[Tt ]\\([0-9][0-9]\\):\\([0-9][0-9]\\):\\([0-9][0-9]\\)\\(\\.[0-9]+\\)?\\([Zz]|[-+]\\([0-9][0-9]\\):\\([0-9][0-9]\\)\\)"


let parse_date_time s =
  (* RFC 3339, date-time *)
  (* TODO: better checks on valid values *)
  match Netstring_str.string_match date_time_re s 0 with
    | None ->
        failwith "Bad date-time"
    | Some m ->
        let year_s = Netstring_str.matched_group m 1 s in
        let month_s = Netstring_str.matched_group m 2 s in
        let mday_s = Netstring_str.matched_group m 3 s in
        let hour_s = Netstring_str.matched_group m 4 s in
        let min_s = Netstring_str.matched_group m 5 s in
        let sec_s = Netstring_str.matched_group m 6 s in
        let frac_s = 
	  try Netstring_str.matched_group m 7 s with Not_found -> "" in
        let tz_s = Netstring_str.matched_group m 8 s in
	let tz_hour_s = 
	  try Netstring_str.matched_group m 9 s with Not_found -> "0" in
	let tz_min_s = 
	  try Netstring_str.matched_group m 10 s with Not_found -> "0" in

	let tz_hour = int_of_string tz_hour_s in
	let tz_min = int_of_string tz_min_s in
	let offs =
	  if tz_s = "Z" then
	    0.0
	  else
	    float(if tz_s.[0] = '+' then
		    tz_hour * 3600 + tz_min * 60
		  else
		    - (tz_hour * 3600 + tz_min * 60)) in

	let year = int_of_string year_s in
	let month = int_of_string month_s in
	let mday = int_of_string mday_s in
	let hour = int_of_string hour_s in
	let min = int_of_string min_s in
	let sec = int_of_string sec_s in
	let frac = if frac_s <> "" then float_of_string frac_s else 0.0 in
	
	let tm =
	  { Unix.tm_year = year - 1900;
	    tm_mon = month - 1;
	    tm_mday = mday;
	    tm_hour = hour;
	    tm_min = min;
	    tm_sec = sec;
	    tm_wday = 0;
	    tm_yday = 0;
	    tm_isdst = false 
	  } in

	(* Little bug: the time offset could change between localtime and
           mktime calls
	 *)
	let t_local, _ = Unix.mktime tm in   (* assumes local time zone *)
	
	let tm_ref = Unix.localtime 0.0 in
	let local_offs = 
	  if tm_ref.Unix.tm_year = 70 then
	    float(tm_ref.Unix.tm_hour * 3600 + tm_ref.Unix.tm_min * 60)
	  else (* 69 *)
	    float(-86400 + tm_ref.Unix.tm_hour * 3600 + tm_ref.Unix.tm_min * 60) in

	let t_utc = t_local +. local_offs in  (* as if we had Z *)
	let t = t_utc -. offs in
	t +. frac


let scan_pcdata node =
  (* Checks that there are no subelements except data *)
  List.iter
    (fun n ->
       if n # node_type <> T_data then
	 failwith "Element nodes found where only data is expected"
    )
    node#sub_nodes;
  node#data


let scan_subelements node f =
  (* Skip over whitespace, and call for every element f *)
  node # iter_nodes
    (fun sub_node ->
       match sub_node # node_type with
	 | T_element _ ->
	     f sub_node
	 | T_data ->
	     if not (Pxp_lib.only_whitespace sub_node#data) then
	       failwith "Character data found at unexpected position in XML"
	 | _ ->
	     failwith "Bad XML node type found"
    )


class write_pinstr_impl node_of_id =
object(self)
  inherit [_] Pxp_document.pinstr_impl Pxp_tree_parser.default_extension

  method display ?prefixes ?minimization out enc =
    (* Instead of printing the processing instruction, look up the
       property, and print the property name
     *)
    let deep =
      self # node_type = T_pinstr "deepnode" in
    let oo_id = int_of_string self#data in
    let node = 
      try node_of_id oo_id with Not_found -> assert false in
    if deep || node # sub_nodes = [] then 
      node # display ?prefixes ?minimization out enc
    else (
      (* There is no non-recursive version of display *)
      let node' = node # orphaned_flat_clone in
      node' # display ?prefixes ?minimization out enc
    )
end


let write
      ?(namespace_manager = namespace_manager())
      ch get_tree =
  (* How the properties are inserted into the printed text: The properties
     can use their own dtd objects and their own namespace managers.
     Because of this, it is impossible to insert the properties into
     the XML tree representing the PROPFIND request. Instead, we use a
     trick. The spots where properties have to be printed are marked
     in the tree with processing instructions, e.g.

     <D:propfind xmlns:D="DAV:">
       <D:allprop/>
       <D:include>
         <? node 1?>   <!-- <D:supported-live-property-set/> -->
         <? node 2?>   <!-- <D:supported-report-set/> -->
       </D:include>
     </D:propfind>
   *)
  (* The tree is obtained by running
     
     [ let tree = get_tree dtd scope nodes ]

     where [nodes] is the hash table mapping node ID's to nodes
     (for property and pre/postcondition codes).
   *)

  let nodes = (Hashtbl.create 10 : (int, property) Hashtbl.t) in
  let dtd = dtd() in
  let mng = dtd#namespace_manager in
  let scope = 
    Pxp_dtd.create_namespace_scope 
      ~decl:mng#as_declaration mng in
  let tree = get_tree dtd scope nodes in

  let node_of_id id =
    try Hashtbl.find nodes id
    with Not_found -> assert false in
  
  (* Now relocate the tree so the following spec is used *)

  let ext = Pxp_tree_parser.default_extension in
  let write_spec = 
    (* Create a new spec so the above class is used for representing
       processing instructions. The display method is redefined there.
     *)
    Pxp_document.make_spec_from_alist
      ~default_pinstr_exemplar:(new write_pinstr_impl node_of_id)
      ~data_exemplar:(new Pxp_document.data_impl ext)
      ~default_element_exemplar:(new Pxp_document.namespace_element_impl ext)
      ~element_alist:[] 
      () in
  
  let tree' =
    Pxp_marshal.relocate_subtree tree dtd write_spec in
  let doc = 
    new Pxp_document.document 
      (new Pxp_types.drop_warnings)
      `Enc_utf8 in
  let out = `Out_netchannel ch in
  doc # init_root tree' "";
  doc # display ~dtd_style:`Omit ~minimization:`AllEmpty out `Enc_utf8


let create_propstat
      ~properties ~status
      ?(status_code = int_of_webdav_status status)
      ?(status_text = string_of_webdav_status status)
      ?(status_protocol = webdav_proto)
      ?(error = [])
      ?(responsedescription = "") () : propstat_t =
  ( object
      method properties = properties
      method status = status
      method status_code = status_code
      method status_text = status_text
      method status_protocol = status_protocol
      method error = error
      method responsedescription = responsedescription
    end
  )


let decode_string p =
  scan_pcdata p
    
let decode_creationdate p =
  parse_date_time (scan_pcdata p)

let decode_displayname p =
  decode_string p

let decode_getcontentlanguage p =
  scan_pcdata p

let decode_getcontentlength p =
  Int64.of_string(scan_pcdata p)

let decode_getcontenttype p =
  let s = scan_pcdata p in
  let t, params = 
    try Mimestring.scan_mime_type s []
    with _ -> 
      failwith "Cannot parse getcontenttype property" in
  (t,params)

let decode_getetag p =
  let s = scan_pcdata p in
  (* etag parser isn't exported by Nethttp - work around: *)
  let h =
    new Netmime.basic_mime_header [ "etag", s ] in
  Nethttp.Header.get_etag h

let decode_getlastmodified p =
  Netdate.since_epoch  (* RFC 1123 date! *)
    (Netdate.parse (scan_pcdata p))

let decode_resourcetype p =
  let is_collection = ref false in
  scan_subelements p
    (fun n ->
       match n # node_type with
	 | T_element "DAV:collection" -> is_collection := true
	 | _ -> ()
    );
  !is_collection



class response href strip_prefix status status_code status_text status_proto 
               propstat error responsedescription location : response_t =
  let flattened_props =
    lazy (
      List.flatten
	(List.map
	   (fun ps ->
	      List.map (fun p -> (p, ps)) ps#properties
	   )
	   propstat)) in
  let ok_props =
    lazy(
      List.map
	fst
	(List.filter
	   (fun (p,ps) -> ps#status_code >= 200 && ps#status_code < 300)
	   (Lazy.force flattened_props)
	)
    ) in
  let find_prop name =
    List.find 
      (fun p -> 
	 match p#node_type with
	   | T_element n -> n=name
	   | _ -> false
      )
      (Lazy.force ok_props) in
  let prop_creationdate = lazy (
    try Some(decode_creationdate (find_prop "DAV:creationdate"))
    with Not_found -> None
  ) in
  let prop_displayname = lazy (
    try Some(decode_displayname(find_prop "DAV:displayname"))
    with Not_found -> None
  ) in
  let prop_getcontentlanguage = lazy (
    try Some(decode_getcontentlanguage(find_prop "DAV:getcontentlanguage"))
    with Not_found -> None
  ) in
  let prop_getcontentlength = lazy (
    try Some(decode_getcontentlength(find_prop "DAV:getcontentlength"))
    with Not_found -> None
  ) in
  let prop_getcontenttype = lazy (
    try Some(scan_pcdata(find_prop "DAV:getcontenttype"))
    with Not_found -> None
  ) in
  let prop_getcontenttype_decoded = lazy (
    try Some(decode_getcontenttype(find_prop "DAV:getcontenttype"))
    with Not_found -> None
  ) in
  let prop_getetag = lazy (
    try Some(scan_pcdata(find_prop "DAV:getetag"))
    with Not_found -> None
  ) in
  let prop_getetag_decoded = lazy (
    try Some(decode_getetag(find_prop "DAV:getetag"))
    with Not_found -> None
  ) in
  let prop_getlastmodified = lazy (
    try Some(decode_getlastmodified(find_prop "DAV:getlastmodified"))
    with Not_found -> None
  ) in
  let prop_resourcetype_is_collection = lazy (
    try Some(decode_resourcetype(find_prop "DAV:resourcetype"))
    with Not_found -> None
  ) in
  let paths =
    List.map (url_path ?strip_prefix) href in

object (self)
  method href = href
  method paths = paths
  method status = status
  method status_code = status_code
  method status_text = status_text
  method status_protocol = status_proto
  method propstat = propstat
  method error = error
  method responsedescription = responsedescription
  method location = location

  method prop_creationdate = Lazy.force prop_creationdate
  method prop_displayname = Lazy.force prop_displayname
  method prop_getcontentlanguage = Lazy.force prop_getcontentlanguage
  method prop_getcontentlength = Lazy.force prop_getcontentlength
  method prop_getcontenttype = Lazy.force prop_getcontenttype
  method prop_getcontenttype_decoded = Lazy.force prop_getcontenttype_decoded
  method prop_getetag = Lazy.force prop_getetag
  method prop_getetag_decoded = Lazy.force prop_getetag_decoded
  method prop_getlastmodified = Lazy.force prop_getlastmodified
  method prop_resourcetype_is_collection = 
    Lazy.force prop_resourcetype_is_collection

  method find_prop name =
    List.find
      (fun (p,ps) ->
	 match p#node_type with
	   | T_element n -> n=name
	   | _ -> false
      )
      (Lazy.force flattened_props)

end


let create_status_response 
      ~href ~status
      ?(status_code = int_of_webdav_status status)
      ?(status_text = string_of_webdav_status status)
      ?(status_protocol = webdav_proto)
      ?(error = [])
      ?(responsedescription = "")
      ?location
      ?strip_prefix
      () =
  if href = [] then
    invalid_arg "Webdav_xml.create_status_response: href must not be empty";
  new response
    href strip_prefix status status_code status_text status_protocol
    [] error responsedescription location


let create_propstat_response 
      ~href ~propstat
      ?(error = [])
      ?(responsedescription = "")
      ?location
      ?strip_prefix
      () =
  if (propstat : propstat_t list) = [] then
    invalid_arg
      "Webdav_xml.create_propstat_response: propstat must not be empty";
  new response
    [href] strip_prefix `Ok 200 "OK" webdav_proto 
    propstat error responsedescription location


let create_multistatus ~responses ?(responsedescription = "") () =
  ( object
      method responses = responses
      method responsedescription = responsedescription
    end
  )


let create_prop ?(namespace_manager = namespace_manager()) f =
  let dtd = Pxp_dtd.create_dtd `Enc_utf8 in
  dtd # allow_arbitrary;
  dtd # set_namespace_manager namespace_manager;
  f dtd

let create_propname ?(namespace_manager = namespace_manager()) name =
  create_prop ~namespace_manager 
    (fun dtd ->
       <:pxp_tree<
         <:autoscope>
           <(name)/>
       >>
    ) ;;

let propname_creationdate = 
  create_propname "DAV:creationdate"
let propname_displayname = 
  create_propname "DAV:displayname"
let propname_getcontentlanguage =
  create_propname "DAV:getcontentlanguage"
let propname_getcontentlength = 
  create_propname "DAV:getcontentlength"
let propname_getcontenttype = 
  create_propname "DAV:getcontenttype"
let propname_getetag = 
  create_propname "DAV:getetag"
let propname_getlastmodified = 
  create_propname "DAV:getlastmodified"
let propname_resourcetype = 
  create_propname "DAV:resourcetype"


let encode_string name s =
  create_prop
    (fun dtd ->
       <:pxp_tree<
         <:autoscope>
           <(name)> <*> s
       >>
    )
    
let encode_creationdate t =
  let s = Netdate.mk_internet_date ~zone:Netdate.localzone t in
  encode_string "DAV:creationdate" s

let encode_displayname s =
  encode_string "DAV:displayname" s

let encode_getcontentlanguage s =
  encode_string "DAV:getcontentlanguage" s

let encode_getcontentlength n = 
  encode_string "DAV:getcontentlength" (Int64.to_string n)

let encode_getcontenttype ct = 
  let h = new Netmime.basic_mime_header [] in
  Nethttp.Header.set_content_type h ct;
  let s = h # field "Content-Type" in
  encode_string "DAV:getcontenttype" s

let encode_getetag t = 
  let h = new Netmime.basic_mime_header [] in
  Nethttp.Header.set_etag h t;
  let s = h # field "Etag" in
  encode_string "DAV:getetag" s

let encode_getlastmodified t = 
  let s = Netdate.mk_mail_date ~zone:Netdate.localzone t in
  encode_string "DAV:getlastmodified" s

let encode_resourcetype b =
  if b then
    create_prop
      (fun dtd ->
	 <:pxp_tree<
           <:autoscope>
             <DAV:resourcetype> <DAV:collection/>
	 >>
      )
  else
    create_propname "DAV:resourcetype"


let create_prepost_code = create_propname


let parse_prepost_code node =
  match node#node_type with
    | T_element "DAV:no-external-entities" -> 
	`No_external_entities
    | T_element "DAV:preserved-live-properties" ->
	`Preserved_live_properties
    | T_element "DAV:propfind-finite-depth" ->
	`Propfind_finite_depth
    | T_element "DAV:cannot-modify-protected-property" ->
	`Cannot_modify_protected_property
    | T_element other ->
	raise Not_found
    | _ ->
	failwith "Webdav_xml.parse_prepost_code: not an element"


let emit_prepost_code code =
  let name =
    match code with
      | `No_external_entities ->
	  "DAV:no-external-entities"
      | `Preserved_live_properties ->
	  "DAV:preserved-live-properties"
      | `Propfind_finite_depth ->
	  "DAV:propfind-finite-depth"
      | `Cannot_modify_protected_property ->
	  "DAV:cannot-modify-protected-property" in
  create_prepost_code name


(* In the following, we analyze the XML tree. Unknown XML elements
   are skipped. The order of sub elements is not enforced.
 *)

let status_re = 
  Netstring_str.regexp
    "^[ \t\r\n]*\\([^ \t]+\\)[ \t]+\\([0-9][0-9][0-9]\\)\\([ \t]+\\([^\r\n]*\\)\\)[ \t\r\n]*$"

let scan_status s =
  match Netstring_str.string_match status_re s 0 with
    | None ->
        failwith "Bad status line"
    | Some m ->
        let proto = Netstring_str.matched_group m 1 s in
        let code_str = Netstring_str.matched_group m 2 s in
        let code = int_of_string code_str in
        let text =
          try Netstring_str.matched_group m 4 s 
          with Not_found -> "" in
        if code < 100 || code > 599 then 
          failwith "Bad status code";
	(proto, code, text)


let parse_responsedescription root =
  scan_pcdata root


let emit_responsedescription dtd scope d =
  <:pxp_tree<
    <:scope>
      <DAV:responsedescription> <*> d
  >>


let parse_href root = 
  (* TODO: check that the URL is ok (only ASCII, and satisfies the 
     production rules)
   *)
  scan_pcdata root


let emit_href dtd scope href =
  <:pxp_tree<
    <:scope>
      <DAV:href> <*> href
  >>


let parse_location root =
  let href_opt = ref None in
  scan_subelements root
    (fun node ->
       match node # node_type with
	 | T_element "DAV:href" ->
	     if !href_opt <> None then
	       failwith "XML structure: double href";
	     let href = parse_href node in
	     href_opt := Some href
	 | _ -> ()
    );
  match !href_opt with
    | None ->
	failwith "XML structure: missing href"
    | Some href ->
	href


let emit_location dtd scope href =
  let href_node = emit_href dtd scope href in
  <:pxp_tree<
    <:scope>
      <DAV:location> [ href_node ]
  >>
  


let parse_prop root =
  (* <!ELEMENT prop ANY > *)
  (* The children elements are the properties *)
  let plist = ref [] in
  scan_subelements root
    (fun node ->
       plist := node :: !plist
    );
  List.rev !plist


let emit_prop dtd scope nodes ~deep props =
  (* deep=true: the property is emitted with contents;
     deep=false: the property is emitted without contents (name only)
   *)
  let pi_list =
    List.map
      (fun p ->
	 let oo_id = string_of_int (Oo.id p) in
	 Hashtbl.replace nodes (Oo.id p) p;
	 if deep then
	   <:pxp_tree<  <?> "deepnode" oo_id >>
	 else
	   <:pxp_tree<  <?> "flatnode" oo_id >>
      )
      props in
  <:pxp_tree<
    <:scope>
      <DAV:prop> pi_list
  >> ;;


let parse_include root =
  let plist = ref [] in
  scan_subelements root
    (fun node ->
       plist := node :: !plist
    );
  List.rev !plist
  


let emit_include dtd scope nodes props =
  let pi_list =
    List.map
      (fun p ->
	 let oo_id = string_of_int (Oo.id p) in
	 Hashtbl.replace nodes (Oo.id p) p;
	 <:pxp_tree<  <?> "flatnode" oo_id >>
      )
      props in
  <:pxp_tree<
    <:scope>
      <DAV:include> pi_list
  >> ;;


let parse_status node =
   scan_status (scan_pcdata node)


let emit_status dtd scope (proto, code, text) =
   let line =
     Printf.sprintf "%s %d %s" proto code text in
   <:pxp_tree<
     <:scope>
       <DAV:status> <*> line
   >>;;


let parse_error node =
   let l = ref [] in
   scan_subelements node 
     (fun sn ->
	try
	  match sn#node_type with
	    | T_element _ ->
		l := sn :: !l
	    | _ -> ()
	with
	  | Not_found -> ()
     );
   List.rev !l


let emit_error dtd scope nodes codes =
  let pi_list =
    List.map
      (fun p ->
	 let oo_id = string_of_int (Oo.id p) in
	 Hashtbl.replace nodes (Oo.id p) p;
	 <:pxp_tree<  <?> "deepnode" oo_id >>
      )
      codes in
  <:pxp_tree<
    <:scope>
      <DAV:error> pi_list
  >> ;;



let parse_propstat root =
  (* <!ELEMENT propstat (prop, status, error?, responsedescription?) > *)
  let prop = ref None in
  let status_code = ref 200 in
  let status_text = ref "OK" in
  let status_protocol = ref "HTTP/1.1 200 OK" in
  let status_seen = ref false in
  let responsedescription = ref None in
  let error = ref None in
  scan_subelements root
    (fun node ->
       match node # node_type with
	 | T_element "DAV:prop" ->
	     if !prop <> None then
	       failwith "XML structure: Double prop";
	     let p = parse_prop node in
	     prop := Some p
	 | T_element "DAV:status" ->
	     if !status_seen then
	       failwith "XML structure: Double status";
	     let (proto, code, text) = parse_status node in
	     status_protocol := proto;
	     status_code := code;
	     status_text := text;
	     status_seen := true
	 | T_element "DAV:responsedescription" ->
	     let d = parse_responsedescription node in
	     if !responsedescription <> None then
	       failwith "XML structure: double responsedescription";
	     responsedescription := Some d
	 | T_element "DAV:error" ->
	     let l = parse_error node in
	     if !error <> None then
	       failwith "XML structure: double error";
	     error := Some l
	 | _ ->
	     ()
    );
  let p =
    match !prop with
      | None -> failwith "XML structure: Missing prop"
      | Some p -> p in
  if not !status_seen then
    failwith "XML structure: Missing status";
  let status = webdav_status_of_int !status_code in
  
  ( object
      method properties = p
      method status = status
      method status_code = !status_code
      method status_text = !status_text
      method status_protocol = !status_protocol
      method responsedescription = 
	match !responsedescription with None -> "" | Some s -> s
      method error =
	match !error with None -> [] | Some l -> l
    end : propstat_t
  )


let prefer ~default p v =
  if p v then v else default


let emit_propstat dtd scope nodes (pstat : propstat_t) =
  let proto = webdav_proto in
  let code = int_of_webdav_status pstat#status in
  let text = string_of_webdav_status pstat#status in
  
  let proto = prefer ~default:proto (fun p -> p <> "") pstat#status_protocol in
  let code = prefer ~default:code (fun c -> c <> 0) pstat#status_code in
  let text = prefer ~default:text (fun t -> t <> "") pstat#status_text in

  <:pxp_tree<
    <:scope>
      <DAV:propstat>
        ( [ (: emit_prop dtd scope nodes ~deep:true pstat#properties :)
	    (: emit_status dtd scope (proto,code,text) :)
          ]
	  @ (: match pstat#error with
		| [] -> []
		| codes -> [ emit_error dtd scope nodes codes ]
	    :)
	  @ (: match pstat#responsedescription with
		| "" -> []
		| d -> [ emit_responsedescription dtd scope d ]
            :)
	)
  >>


let parse_response strip_prefix root =
  (* <!ELEMENT response (href, ((href*, status)|(propstat+)),
                         error?, responsedescription? , location?) > *)
  let href_list = ref [] in
  let status_code = ref 200 in
  let status_text = ref "OK" in
  let status_protocol = ref "HTTP/1.1 200 OK" in
  let status_seen = ref false in
  let propstat_list = ref [] in
  let responsedescription = ref None in
  let location = ref None in
  let error = ref None in
  scan_subelements root
    (fun node ->
       match node # node_type with
	 | T_element "DAV:href" ->
	     let href = parse_href node in
	     href_list := href :: !href_list
	 | T_element "DAV:status" ->
	     if !status_seen then
	       failwith "XML structure: Double status";
	     if !propstat_list <> [] then
	       failwith "XML structure: Cannot mix status and propstat";
	     let (proto, code, text) = scan_status (scan_pcdata node) in
	     status_protocol := proto;
	     status_code := code;
	     status_text := text;
	     status_seen := true
	 | T_element "DAV:propstat" ->
	     if !status_seen then
	       failwith "XML structure: Cannot mix status and propstat";
	     let ps = parse_propstat node in
	     propstat_list := ps :: !propstat_list
	 | T_element "DAV:responsedescription" ->
	     let d = parse_responsedescription node in
	     if !responsedescription <> None then
	       failwith "XML structure: double responsedescription";
	     responsedescription := Some d
	 | T_element "DAV:location" ->
	     let d = parse_location node in
	     if !location <> None then
	       failwith "XML structure: double location";
	     location := Some d
	 | T_element "DAV:error" ->
	     let e = parse_error node in
	     if !error <> None then
	       failwith "XML structure: double error";
	     error := Some e
	 | _ ->
	     ()
    );
  if not !status_seen && !propstat_list = [] then
    failwith "XML structure: Neither status nor propstat found";
  href_list := List.rev !href_list;
  propstat_list := List.rev !propstat_list;
  let status = webdav_status_of_int !status_code in
  new response 
    !href_list strip_prefix status !status_code !status_text !status_protocol
    !propstat_list 
    (match !error with None -> [] | Some l -> l)
    (match !responsedescription with None -> "" | Some s -> s)
    !location


let emit_response dtd scope nodes (resp : response_t) =
  let status_or_propstats =
    if resp#propstat = [] then
      [ emit_status
	  dtd scope
	  (resp#status_protocol, resp#status_code, resp#status_text)
      ]
    else
      List.map (emit_propstat dtd scope nodes) resp#propstat in
  <:pxp_tree<
    <:scope>
      <DAV:response>
        ( (: List.map (emit_href dtd scope) resp#href :)
	  @ status_or_propstats
	  @ (: match resp#error with
		| [] -> []
		| codes -> [ emit_error dtd scope nodes codes ]
	    :)
	  @ (: match resp#responsedescription with
		| "" -> []
		| d -> [ emit_responsedescription dtd scope d ]
            :)
	  @ (: match resp#location with
		| None -> []
		| Some loc -> [ emit_location dtd scope loc ]
	    :)
	)
  >>
  

let parse_multistatus strip_prefix root =
  (* Entry point; hence check root type *)
  ( match root#node_type with
      | T_element "DAV:multistatus" -> ()
      | _ ->
	  failwith "The XML document is not a multistatus message"
  );
  (* <!ELEMENT multistatus (response*, responsedescription?)  > *)
  let responses = ref [] in
  let responsedescription = ref None in
  scan_subelements root
    (fun node ->
       match node # node_type with
	 | T_element "DAV:response" ->
	     let r = parse_response strip_prefix node in
	     responses := r :: !responses;
	 | T_element "DAV:responsedescription" ->
	     let d = parse_responsedescription node in
	     if !responsedescription <> None then
	       failwith "XML structure: double responsedescription";
	     responsedescription := Some d
	 | _ -> ()
    );
  responses := List.rev !responses;
  ( object
      method responses = !responses
      method responsedescription =
	match !responsedescription with
	  | None -> ""
	  | Some s -> s
    end
  )


let emit_multistatus dtd scope nodes (mstat : multistatus_t) =
  <:pxp_tree<
    <:scope>
      <DAV:multistatus>
        ( (: List.map (emit_response dtd scope nodes) mstat#responses :)
	  @ (: match mstat#responsedescription with
		| "" -> []
		| d -> [ emit_responsedescription dtd scope d ]
            :)
	)
  >> ;;


let parse_body ~namespace_manager ~content_type ch =
  let _, params = 
    try Mimestring.scan_mime_type content_type []
    with _ -> failwith ("Cannot parse Content-type: " ^ content_type) in
  let content_type_encoding =
    try
      let e = List.assoc "charset" params in
      try
	Some(Netconversion.encoding_of_string e)
      with
	| _ -> failwith ("Unknown charset: " ^ e)
    with
      | Not_found -> None in

  let config = 
    { Pxp_types.default_config with
	Pxp_types.encoding = `Enc_utf8;
	store_element_positions = false;
	enable_namespace_processing = Some namespace_manager;
    } in

  let source = 
    (* The source must reliably prevent that external entities can be
       resolved. This is true for [from_obj_channel] by default.

       If there is a charset in content_type, enforce that this encoding
       is used.
     *)
    Pxp_types.from_obj_channel
      ?fixenc:content_type_encoding
      ch in

  let root = 
    try
      (Pxp_tree_parser.parse_wfdocument_entity
	 ~transform_dtd:(fun dtd -> check_dtd dtd; dtd)
	 config
	 source
	 spec) # root
    with
      | error ->
	  failwith ("Cannot parse XML message: " ^ 
		      Pxp_types.string_of_exn error) in
  root

	

let parse_multistatus_body
      ?strip_prefix
      ?(namespace_manager = namespace_manager())
      ~content_type
      ch =
  
  let root = parse_body ~namespace_manager ~content_type ch in
  parse_multistatus strip_prefix root


let write_multistatus_body ?namespace_manager ch mstat =
  write
    ?namespace_manager
    ch
    (fun dtd scope nodes ->
       emit_multistatus dtd scope nodes mstat
    )

let parse_propfind root =
  (* Entry point; hence check root type *)
  ( match root#node_type with
      | T_element "DAV:propfind" -> ()
      | _ ->
	  failwith "The XML document is not a propfind message"
  );
  let req = ref None in
  scan_subelements root
    (fun node ->
       match node # node_type with
	 | T_element "DAV:propname" ->
	     if !req <> None then
	       failwith "XML structure: bad propfind";
	     req := Some `Propname
	 | T_element "DAV:allprop" ->
	     if !req <> None then
	       failwith "XML structure: bad propfind";
	     req := Some (`Allprop [])
	 | T_element "DAV:include" ->
	     ( match !req with
		 | Some (`Allprop []) ->
		     let props = parse_include node in
		     req := Some(`Allprop props)
		 | _ ->
		     failwith "XML structure: bad propfind";
	     )
	 | T_element "DAV:prop" ->
	     if !req <> None then
	       failwith "XML structure: bad propfind";
	     let props = parse_prop node in
	     req := Some(`Prop props)
	 | _ ->
	     ()
    );
  match !req with
    | None ->
	failwith "XML structure: bad propfind"
    | Some r ->
	r


let parse_propfind_request
      ?(namespace_manager = namespace_manager())
      ~content_type
      ch : propfind_request =
  
  let root = parse_body ~namespace_manager ~content_type ch in
  parse_propfind root


let write_propfind_request
      ?namespace_manager
      ch req =

  write
    ?namespace_manager
    ch
    (fun dtd scope nodes ->
       let t =
	 match req with
	   | `Prop l ->
	       [ emit_prop dtd scope nodes ~deep:false l ]
	   | `Propname ->
	       [ <:pxp_tree<
                   <:scope>
                      <DAV:propname>
                         []
                 >>
	       ]
	   | `Allprop [] ->
	       [ <:pxp_tree<
                   <:scope>
                     <DAV:allprop>
                       []
                 >>
	       ]
	   | `Allprop l ->
	       [ <:pxp_tree< <:scope> <DAV:allprop/> >>;
		 emit_include dtd scope nodes l
               ] in
       <:pxp_tree<
         <:scope>
           <DAV:propfind>
	     t
       >>
    )


let parse_remove root =
  let instr = ref None in
  scan_subelements root
    (fun node ->
       match node # node_type with
	 | T_element "DAV:prop" ->
	     if !instr <> None then
	       failwith "XML structure: bad remove";
	     instr := Some(parse_prop node)
	 | _ -> ()
    );
  match !instr with
    | None ->
	failwith "XML structure: bad remove"
    | Some i ->
	i


let parse_set root =
  let instr = ref None in
  scan_subelements root
    (fun node ->
       match node # node_type with
	 | T_element "DAV:prop" ->
	     if !instr <> None then
	       failwith "XML structure: bad set";
	     instr := Some(parse_prop node)
	 | _ -> ()
    );
  match !instr with
    | None ->
	failwith "XML structure: bad set"
    | Some i ->
	i


let parse_propertyupdate root =
  (* Entry point; hence check root type *)
  ( match root#node_type with
      | T_element "DAV:propertyupdate" -> ()
      | _ ->
	  failwith "The XML document is not a proppatch message"
  );
  let update = ref [] in
  scan_subelements root
    (fun node ->
       match node # node_type with
	 | T_element "DAV:remove" ->
	     update := (`Remove (parse_remove node)) :: !update
	 | T_element "DAV:set" ->
	     update := (`Set (parse_set node)) :: !update
	 | _ ->
	     ()
    );
  List.rev !update


let parse_proppatch_request
      ?(namespace_manager = namespace_manager())
      ~content_type
      ch =
  
  let root = parse_body ~namespace_manager ~content_type ch in
  parse_propertyupdate root


let write_proppatch_request
      ?namespace_manager
      ch req =

  write
    ?namespace_manager
    ch
    (fun dtd scope nodes ->
       let t =
	 List.flatten
	   (List.map
	      (function
		 | `Remove [] -> []
		 | `Remove l ->
		     [ <:pxp_tree<
		         <:scope>
		           <DAV:remove>
                              [ (: emit_prop dtd scope nodes ~deep:false l :) ]
		       >>
		     ]
		 | `Set [] -> []
		 | `Set l ->
		     [ <:pxp_tree<
		         <:scope>
		           <DAV:set>
                              [ (: emit_prop dtd scope nodes ~deep:true l :) ]
		       >>
		     ]
	      )
	      req
	   ) in
       if t=[] then
	 failwith "Webdav_xml: proppatch is empty";
       <:pxp_tree<
	 <:scope>
	   <DAV:propertyupdate> t
       >>
    )


		       

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