2026-02-14 01:29:34 +01:00
|
|
|
use crate::application::dtos::file_dto::FileDto;
|
|
|
|
|
use crate::application::dtos::folder_dto::FolderDto;
|
|
|
|
|
use chrono::Utc;
|
|
|
|
|
use quick_xml::{
|
|
|
|
|
Reader, Writer,
|
|
|
|
|
events::{BytesEnd, BytesStart, BytesText, Event},
|
|
|
|
|
};
|
2025-04-04 01:48:55 +02:00
|
|
|
/**
|
|
|
|
|
* WebDAV Adapter Module
|
2026-02-14 01:29:34 +01:00
|
|
|
*
|
2025-04-04 21:31:41 +02:00
|
|
|
* This module provides conversion between WebDAV protocol XML structures and OxiCloud domain objects.
|
|
|
|
|
* It handles parsing WebDAV request XML and generating WebDAV response XML according to RFC 4918.
|
2025-04-04 01:48:55 +02:00
|
|
|
*/
|
2026-02-14 01:29:34 +01:00
|
|
|
use std::io::{BufReader, Read, Write};
|
2025-04-04 01:48:55 +02:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Result type for WebDAV operations
|
|
|
|
|
pub type Result<T> = std::result::Result<T, WebDavError>;
|
|
|
|
|
|
|
|
|
|
/// Error type for WebDAV operations
|
|
|
|
|
#[derive(Debug)]
|
2025-04-04 01:48:55 +02:00
|
|
|
pub enum WebDavError {
|
2025-04-04 21:31:41 +02:00
|
|
|
XmlError(quick_xml::Error),
|
|
|
|
|
IoError(std::io::Error),
|
|
|
|
|
ParseError(String),
|
2025-04-04 01:48:55 +02:00
|
|
|
}
|
|
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
impl From<quick_xml::Error> for WebDavError {
|
|
|
|
|
fn from(err: quick_xml::Error) -> Self {
|
|
|
|
|
WebDavError::XmlError(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-04-04 01:48:55 +02:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
impl From<std::io::Error> for WebDavError {
|
|
|
|
|
fn from(err: std::io::Error) -> Self {
|
|
|
|
|
WebDavError::IoError(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for WebDavError {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
WebDavError::XmlError(e) => write!(f, "XML error: {}", e),
|
|
|
|
|
WebDavError::IoError(e) => write!(f, "IO error: {}", e),
|
|
|
|
|
WebDavError::ParseError(msg) => write!(f, "Parse error: {}", msg),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Qualified name with namespace and local name
|
|
|
|
|
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
|
|
|
|
pub struct QualifiedName {
|
2025-04-04 01:48:55 +02:00
|
|
|
pub namespace: String,
|
|
|
|
|
pub name: String,
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
impl QualifiedName {
|
|
|
|
|
pub fn new<S: Into<String>>(namespace: S, name: S) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
namespace: namespace.into(),
|
|
|
|
|
name: name.into(),
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-15 17:53:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for QualifiedName {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
2025-04-04 21:31:41 +02:00
|
|
|
if self.namespace.is_empty() {
|
2026-02-15 17:53:25 +01:00
|
|
|
write!(f, "{}", self.name)
|
2025-04-04 21:31:41 +02:00
|
|
|
} else {
|
2026-02-15 17:53:25 +01:00
|
|
|
write!(f, "{{{}}}{}", self.namespace, self.name)
|
2025-04-04 21:31:41 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// PROPFIND request type
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
pub enum PropFindType {
|
|
|
|
|
/// Request all properties
|
|
|
|
|
AllProp,
|
|
|
|
|
/// Request property names only
|
|
|
|
|
PropName,
|
|
|
|
|
/// Request specific properties
|
|
|
|
|
Prop(Vec<QualifiedName>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// PROPFIND request
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct PropFindRequest {
|
|
|
|
|
pub prop_find_type: PropFindType,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// WebDAV property value
|
2025-04-04 01:48:55 +02:00
|
|
|
#[derive(Debug, Clone)]
|
2025-04-04 21:31:41 +02:00
|
|
|
pub struct PropValue {
|
|
|
|
|
pub name: QualifiedName,
|
2025-04-04 01:48:55 +02:00
|
|
|
pub value: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// WebDAV lock information
|
2025-04-04 01:48:55 +02:00
|
|
|
#[derive(Debug, Clone)]
|
2025-04-04 21:31:41 +02:00
|
|
|
pub struct LockInfo {
|
|
|
|
|
pub token: String,
|
|
|
|
|
pub owner: Option<String>,
|
|
|
|
|
pub depth: String,
|
|
|
|
|
pub timeout: Option<String>,
|
|
|
|
|
pub scope: LockScope,
|
|
|
|
|
pub type_: LockType,
|
2025-04-04 01:48:55 +02:00
|
|
|
}
|
|
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Lock scope (exclusive or shared)
|
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
|
pub enum LockScope {
|
|
|
|
|
Exclusive,
|
|
|
|
|
Shared,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Lock type (currently only write)
|
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
|
pub enum LockType {
|
|
|
|
|
Write,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// WebDAV adapter for converting between XML and domain objects
|
2025-04-04 01:48:55 +02:00
|
|
|
pub struct WebDavAdapter;
|
|
|
|
|
|
|
|
|
|
impl WebDavAdapter {
|
2026-03-02 20:40:41 +00:00
|
|
|
/// Collect namespace prefix → URI mappings from element attributes.
|
|
|
|
|
/// E.g. `xmlns:D="DAV:"` maps prefix `"D"` to `"DAV:"`.
|
|
|
|
|
pub fn collect_ns_decls(
|
|
|
|
|
e: &BytesStart,
|
|
|
|
|
ns_map: &mut std::collections::HashMap<String, String>,
|
|
|
|
|
) {
|
|
|
|
|
for attr in e.attributes().flatten() {
|
|
|
|
|
let key = std::str::from_utf8(attr.key.as_ref()).unwrap_or("");
|
|
|
|
|
if let Some(prefix) = key.strip_prefix("xmlns:") {
|
|
|
|
|
let uri = attr.unescape_value().unwrap_or_default().to_string();
|
|
|
|
|
ns_map.insert(prefix.to_string(), uri);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Resolve a prefixed element name (e.g. `D:resourcetype`) to a
|
|
|
|
|
/// `QualifiedName` using the accumulated namespace declarations.
|
|
|
|
|
pub fn resolve_name(
|
|
|
|
|
name_str: &str,
|
|
|
|
|
ns_map: &std::collections::HashMap<String, String>,
|
|
|
|
|
) -> QualifiedName {
|
|
|
|
|
if let Some(idx) = name_str.find(':') {
|
|
|
|
|
let prefix = &name_str[..idx];
|
|
|
|
|
let local = &name_str[idx + 1..];
|
|
|
|
|
if let Some(uri) = ns_map.get(prefix) {
|
|
|
|
|
return QualifiedName::new(uri.clone(), local.to_string());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Fallback: no prefix or unknown prefix → use legacy extraction
|
|
|
|
|
QualifiedName::new(
|
|
|
|
|
Self::extract_namespace(name_str),
|
|
|
|
|
Self::extract_local_name(name_str),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Parse a PROPFIND XML request
|
2025-04-04 01:48:55 +02:00
|
|
|
pub fn parse_propfind<R: Read>(reader: R) -> Result<PropFindRequest> {
|
2025-04-04 21:31:41 +02:00
|
|
|
let mut xml_reader = Reader::from_reader(BufReader::new(reader));
|
2025-04-13 01:04:04 +02:00
|
|
|
xml_reader.config_mut().trim_text(true);
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
let mut buffer = Vec::new();
|
|
|
|
|
let mut in_propfind = false;
|
|
|
|
|
let mut in_prop = false;
|
|
|
|
|
let mut in_allprop = false;
|
|
|
|
|
let mut in_propname = false;
|
|
|
|
|
let mut props = Vec::new();
|
2026-03-02 20:40:41 +00:00
|
|
|
let mut ns_map = std::collections::HashMap::<String, String>::new();
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 01:48:55 +02:00
|
|
|
loop {
|
2025-04-04 21:31:41 +02:00
|
|
|
match xml_reader.read_event_into(&mut buffer) {
|
2025-04-04 01:48:55 +02:00
|
|
|
Ok(Event::Start(ref e)) => {
|
2026-03-02 20:40:41 +00:00
|
|
|
Self::collect_ns_decls(e, &mut ns_map);
|
2025-04-04 01:48:55 +02:00
|
|
|
let name = e.name();
|
2025-04-04 21:31:41 +02:00
|
|
|
let name_str = std::str::from_utf8(name.as_ref()).unwrap_or("");
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
if name_str == "propfind" || name_str.ends_with(":propfind") {
|
|
|
|
|
in_propfind = true;
|
|
|
|
|
} else if in_propfind && (name_str == "prop" || name_str.ends_with(":prop")) {
|
|
|
|
|
in_prop = true;
|
2026-02-14 01:29:34 +01:00
|
|
|
} else if in_propfind
|
|
|
|
|
&& (name_str == "allprop" || name_str.ends_with(":allprop"))
|
|
|
|
|
{
|
2025-04-04 21:31:41 +02:00
|
|
|
in_allprop = true;
|
2026-02-14 01:29:34 +01:00
|
|
|
} else if in_propfind
|
|
|
|
|
&& (name_str == "propname" || name_str.ends_with(":propname"))
|
|
|
|
|
{
|
2025-04-04 21:31:41 +02:00
|
|
|
in_propname = true;
|
|
|
|
|
} else if in_prop {
|
2026-03-02 20:40:41 +00:00
|
|
|
let qname = Self::resolve_name(name_str, &ns_map);
|
|
|
|
|
props.push(qname);
|
2025-04-04 01:48:55 +02:00
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
Ok(Event::End(ref e)) => {
|
2025-04-04 01:48:55 +02:00
|
|
|
let name = e.name();
|
2025-04-04 21:31:41 +02:00
|
|
|
let name_str = std::str::from_utf8(name.as_ref()).unwrap_or("");
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
if name_str == "propfind" || name_str.ends_with(":propfind") {
|
|
|
|
|
in_propfind = false;
|
|
|
|
|
} else if name_str == "prop" || name_str.ends_with(":prop") {
|
|
|
|
|
in_prop = false;
|
|
|
|
|
} else if name_str == "allprop" || name_str.ends_with(":allprop") {
|
|
|
|
|
in_allprop = false;
|
|
|
|
|
} else if name_str == "propname" || name_str.ends_with(":propname") {
|
|
|
|
|
in_propname = false;
|
2025-04-04 01:48:55 +02:00
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
Ok(Event::Empty(ref e)) => {
|
2026-03-02 20:40:41 +00:00
|
|
|
Self::collect_ns_decls(e, &mut ns_map);
|
2025-04-04 01:48:55 +02:00
|
|
|
let name = e.name();
|
2025-04-04 21:31:41 +02:00
|
|
|
let name_str = std::str::from_utf8(name.as_ref()).unwrap_or("");
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
if in_propfind && (name_str == "allprop" || name_str.ends_with(":allprop")) {
|
|
|
|
|
in_allprop = true;
|
2026-02-14 01:29:34 +01:00
|
|
|
} else if in_propfind
|
|
|
|
|
&& (name_str == "propname" || name_str.ends_with(":propname"))
|
|
|
|
|
{
|
2025-04-04 21:31:41 +02:00
|
|
|
in_propname = true;
|
|
|
|
|
} else if in_prop {
|
2026-03-02 20:40:41 +00:00
|
|
|
let qname = Self::resolve_name(name_str, &ns_map);
|
|
|
|
|
props.push(qname);
|
2025-04-04 01:48:55 +02:00
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 01:48:55 +02:00
|
|
|
Ok(Event::Eof) => break,
|
2025-04-04 21:31:41 +02:00
|
|
|
Err(e) => return Err(WebDavError::XmlError(e)),
|
2025-04-04 01:48:55 +02:00
|
|
|
_ => (),
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
buffer.clear();
|
2025-04-04 01:48:55 +02:00
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
let prop_find_type = if in_allprop {
|
|
|
|
|
PropFindType::AllProp
|
|
|
|
|
} else if in_propname {
|
|
|
|
|
PropFindType::PropName
|
|
|
|
|
} else {
|
|
|
|
|
PropFindType::Prop(props)
|
|
|
|
|
};
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
Ok(PropFindRequest { prop_find_type })
|
2025-04-04 01:48:55 +02:00
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Write folder properties as a response
|
|
|
|
|
fn write_folder_response<W: Write>(
|
|
|
|
|
xml_writer: &mut Writer<W>,
|
|
|
|
|
folder: &FolderDto,
|
2025-04-04 01:48:55 +02:00
|
|
|
request: &PropFindRequest,
|
2025-04-04 21:31:41 +02:00
|
|
|
href: &str,
|
2025-04-04 01:48:55 +02:00
|
|
|
) -> Result<()> {
|
2025-04-04 21:31:41 +02:00
|
|
|
// Start response element
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:response")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write href
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:href")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(href)))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:href")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write propstat
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Start prop
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:prop")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write properties based on request type
|
|
|
|
|
match &request.prop_find_type {
|
|
|
|
|
PropFindType::AllProp => {
|
|
|
|
|
// Write all standard properties for a folder
|
|
|
|
|
Self::write_folder_standard_props(xml_writer, folder)?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
PropFindType::PropName => {
|
|
|
|
|
// Write only property names (empty elements)
|
|
|
|
|
Self::write_folder_prop_names(xml_writer)?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
PropFindType::Prop(props) => {
|
|
|
|
|
// Write requested properties
|
|
|
|
|
Self::write_folder_requested_props(xml_writer, folder, props)?;
|
2025-04-04 01:48:55 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// End prop
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:prop")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write status
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:status")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new("HTTP/1.1 200 OK")))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:status")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// End propstat
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:propstat")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// End response
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:response")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 01:48:55 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Write file properties as a response
|
|
|
|
|
fn write_file_response<W: Write>(
|
2025-04-04 01:48:55 +02:00
|
|
|
xml_writer: &mut Writer<W>,
|
2025-04-04 21:31:41 +02:00
|
|
|
file: &FileDto,
|
2025-04-04 01:48:55 +02:00
|
|
|
request: &PropFindRequest,
|
2025-04-04 21:31:41 +02:00
|
|
|
href: &str,
|
2025-04-04 01:48:55 +02:00
|
|
|
) -> Result<()> {
|
|
|
|
|
// Start response element
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:response")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 01:48:55 +02:00
|
|
|
// Write href
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:href")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(href)))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:href")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write propstat
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 01:48:55 +02:00
|
|
|
// Start prop
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:prop")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write properties based on request type
|
|
|
|
|
match &request.prop_find_type {
|
|
|
|
|
PropFindType::AllProp => {
|
|
|
|
|
// Write all standard properties for a file
|
|
|
|
|
Self::write_file_standard_props(xml_writer, file)?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
PropFindType::PropName => {
|
|
|
|
|
// Write only property names (empty elements)
|
|
|
|
|
Self::write_file_prop_names(xml_writer)?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
PropFindType::Prop(props) => {
|
|
|
|
|
// Write requested properties
|
|
|
|
|
Self::write_file_requested_props(xml_writer, file, props)?;
|
2025-04-04 01:48:55 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 01:48:55 +02:00
|
|
|
// End prop
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:prop")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 01:48:55 +02:00
|
|
|
// Write status
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:status")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new("HTTP/1.1 200 OK")))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:status")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 01:48:55 +02:00
|
|
|
// End propstat
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:propstat")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 01:48:55 +02:00
|
|
|
// End response
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:response")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 01:48:55 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Write standard folder properties
|
|
|
|
|
fn write_folder_standard_props<W: Write>(
|
2025-04-04 01:48:55 +02:00
|
|
|
xml_writer: &mut Writer<W>,
|
2025-04-04 21:31:41 +02:00
|
|
|
folder: &FolderDto,
|
2025-04-04 01:48:55 +02:00
|
|
|
) -> Result<()> {
|
2025-04-04 21:31:41 +02:00
|
|
|
// Resource type (collection)
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:resourcetype")))?;
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:collection")))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:resourcetype")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Display name
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:displayname")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&folder.name)))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:displayname")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Creation date
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:creationdate")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Convert u64 timestamp to DateTime
|
|
|
|
|
let created_at = chrono::DateTime::<Utc>::from_timestamp(folder.created_at as i64, 0)
|
2026-02-14 01:26:02 +01:00
|
|
|
.unwrap_or_else(Utc::now);
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&created_at.to_rfc3339())))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:creationdate")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Last modified
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:getlastmodified")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Convert u64 timestamp to DateTime
|
|
|
|
|
let modified_at = chrono::DateTime::<Utc>::from_timestamp(folder.modified_at as i64, 0)
|
2026-02-14 01:26:02 +01:00
|
|
|
.unwrap_or_else(Utc::now);
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&modified_at.to_rfc2822())))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:getlastmodified")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Other standard properties
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:getetag")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&format!("\"{}\"", folder.id))))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:getetag")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Content length (0 for directories)
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:getcontentlength")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new("0")))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:getcontentlength")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Content type for directories
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:getcontenttype")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new("httpd/unix-directory")))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:getcontenttype")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 01:48:55 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Write standard file properties
|
|
|
|
|
fn write_file_standard_props<W: Write>(
|
|
|
|
|
xml_writer: &mut Writer<W>,
|
|
|
|
|
file: &FileDto,
|
|
|
|
|
) -> Result<()> {
|
|
|
|
|
// Resource type (empty for files)
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:resourcetype")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Display name
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:displayname")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&file.name)))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:displayname")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Content type
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:getcontenttype")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&file.mime_type)))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:getcontenttype")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Content length
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:getcontentlength")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&file.size.to_string())))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:getcontentlength")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Creation date
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:creationdate")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Convert u64 timestamp to DateTime
|
|
|
|
|
let created_at = chrono::DateTime::<Utc>::from_timestamp(file.created_at as i64, 0)
|
2026-02-14 01:26:02 +01:00
|
|
|
.unwrap_or_else(Utc::now);
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&created_at.to_rfc3339())))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:creationdate")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Last modified
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:getlastmodified")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Convert u64 timestamp to DateTime
|
|
|
|
|
let modified_at = chrono::DateTime::<Utc>::from_timestamp(file.modified_at as i64, 0)
|
2026-02-14 01:26:02 +01:00
|
|
|
.unwrap_or_else(Utc::now);
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&modified_at.to_rfc2822())))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:getlastmodified")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// ETag
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:getetag")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&format!("\"{}\"", file.id))))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:getetag")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 01:48:55 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Write folder property names
|
2026-02-14 01:29:34 +01:00
|
|
|
fn write_folder_prop_names<W: Write>(xml_writer: &mut Writer<W>) -> Result<()> {
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write empty property elements for folders
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:resourcetype")))?;
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:displayname")))?;
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:creationdate")))?;
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:getlastmodified")))?;
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:getetag")))?;
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:getcontentlength")))?;
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:getcontenttype")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 01:48:55 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Write file property names
|
2026-02-14 01:29:34 +01:00
|
|
|
fn write_file_prop_names<W: Write>(xml_writer: &mut Writer<W>) -> Result<()> {
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write empty property elements for files
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:resourcetype")))?;
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:displayname")))?;
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:getcontenttype")))?;
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:getcontentlength")))?;
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:creationdate")))?;
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:getlastmodified")))?;
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:getetag")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 01:48:55 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Write requested folder properties
|
|
|
|
|
fn write_folder_requested_props<W: Write>(
|
|
|
|
|
xml_writer: &mut Writer<W>,
|
|
|
|
|
folder: &FolderDto,
|
|
|
|
|
props: &[QualifiedName],
|
|
|
|
|
) -> Result<()> {
|
|
|
|
|
for prop in props {
|
|
|
|
|
if prop.namespace == "DAV:" {
|
|
|
|
|
match prop.name.as_str() {
|
|
|
|
|
"resourcetype" => {
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:resourcetype")))?;
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:collection")))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:resourcetype")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
"displayname" => {
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:displayname")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&folder.name)))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:displayname")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
"creationdate" => {
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:creationdate")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Convert u64 timestamp to DateTime
|
2026-02-14 01:29:34 +01:00
|
|
|
let created_at =
|
|
|
|
|
chrono::DateTime::<Utc>::from_timestamp(folder.created_at as i64, 0)
|
|
|
|
|
.unwrap_or_else(Utc::now);
|
|
|
|
|
|
|
|
|
|
xml_writer
|
|
|
|
|
.write_event(Event::Text(BytesText::new(&created_at.to_rfc3339())))?;
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:creationdate")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
"getlastmodified" => {
|
2026-02-14 01:29:34 +01:00
|
|
|
xml_writer
|
|
|
|
|
.write_event(Event::Start(BytesStart::new("D:getlastmodified")))?;
|
|
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Convert u64 timestamp to DateTime
|
2026-02-14 01:29:34 +01:00
|
|
|
let modified_at =
|
|
|
|
|
chrono::DateTime::<Utc>::from_timestamp(folder.modified_at as i64, 0)
|
|
|
|
|
.unwrap_or_else(Utc::now);
|
|
|
|
|
|
|
|
|
|
xml_writer
|
|
|
|
|
.write_event(Event::Text(BytesText::new(&modified_at.to_rfc2822())))?;
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:getlastmodified")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
"getetag" => {
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:getetag")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&format!(
|
|
|
|
|
"\"{}\"",
|
|
|
|
|
folder.id
|
|
|
|
|
))))?;
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:getetag")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
"getcontentlength" => {
|
2026-02-14 01:29:34 +01:00
|
|
|
xml_writer
|
|
|
|
|
.write_event(Event::Start(BytesStart::new("D:getcontentlength")))?;
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::Text(BytesText::new("0")))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:getcontentlength")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
"getcontenttype" => {
|
2026-02-14 01:29:34 +01:00
|
|
|
xml_writer
|
|
|
|
|
.write_event(Event::Start(BytesStart::new("D:getcontenttype")))?;
|
|
|
|
|
xml_writer
|
|
|
|
|
.write_event(Event::Text(BytesText::new("httpd/unix-directory")))?;
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:getcontenttype")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
_ => {
|
|
|
|
|
// Property not supported - write empty element
|
2026-02-14 01:29:34 +01:00
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new(format!(
|
|
|
|
|
"D:{}",
|
|
|
|
|
prop.name
|
|
|
|
|
))))?;
|
2025-04-04 21:31:41 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Non-DAV namespace, not supported
|
2026-02-14 01:29:34 +01:00
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new(format!(
|
|
|
|
|
"{}:{}",
|
|
|
|
|
prop.namespace, prop.name
|
|
|
|
|
))))?;
|
2025-04-04 21:31:41 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 01:48:55 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Write requested file properties
|
|
|
|
|
fn write_file_requested_props<W: Write>(
|
|
|
|
|
xml_writer: &mut Writer<W>,
|
|
|
|
|
file: &FileDto,
|
|
|
|
|
props: &[QualifiedName],
|
|
|
|
|
) -> Result<()> {
|
|
|
|
|
for prop in props {
|
|
|
|
|
if prop.namespace == "DAV:" {
|
|
|
|
|
match prop.name.as_str() {
|
|
|
|
|
"resourcetype" => {
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:resourcetype")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
"displayname" => {
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:displayname")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&file.name)))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:displayname")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
"getcontenttype" => {
|
2026-02-14 01:29:34 +01:00
|
|
|
xml_writer
|
|
|
|
|
.write_event(Event::Start(BytesStart::new("D:getcontenttype")))?;
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&file.mime_type)))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:getcontenttype")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
"getcontentlength" => {
|
2026-02-14 01:29:34 +01:00
|
|
|
xml_writer
|
|
|
|
|
.write_event(Event::Start(BytesStart::new("D:getcontentlength")))?;
|
|
|
|
|
xml_writer
|
|
|
|
|
.write_event(Event::Text(BytesText::new(&file.size.to_string())))?;
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:getcontentlength")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
"creationdate" => {
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:creationdate")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Convert u64 timestamp to DateTime
|
2026-02-14 01:29:34 +01:00
|
|
|
let created_at =
|
|
|
|
|
chrono::DateTime::<Utc>::from_timestamp(file.created_at as i64, 0)
|
|
|
|
|
.unwrap_or_else(Utc::now);
|
|
|
|
|
|
|
|
|
|
xml_writer
|
|
|
|
|
.write_event(Event::Text(BytesText::new(&created_at.to_rfc3339())))?;
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:creationdate")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
"getlastmodified" => {
|
2026-02-14 01:29:34 +01:00
|
|
|
xml_writer
|
|
|
|
|
.write_event(Event::Start(BytesStart::new("D:getlastmodified")))?;
|
|
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Convert u64 timestamp to DateTime
|
2026-02-14 01:29:34 +01:00
|
|
|
let modified_at =
|
|
|
|
|
chrono::DateTime::<Utc>::from_timestamp(file.modified_at as i64, 0)
|
|
|
|
|
.unwrap_or_else(Utc::now);
|
|
|
|
|
|
|
|
|
|
xml_writer
|
|
|
|
|
.write_event(Event::Text(BytesText::new(&modified_at.to_rfc2822())))?;
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:getlastmodified")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
"getetag" => {
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:getetag")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&format!(
|
|
|
|
|
"\"{}\"",
|
|
|
|
|
file.id
|
|
|
|
|
))))?;
|
2025-04-04 21:31:41 +02:00
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:getetag")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
_ => {
|
|
|
|
|
// Property not supported - write empty element
|
2026-02-14 01:29:34 +01:00
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new(format!(
|
|
|
|
|
"D:{}",
|
|
|
|
|
prop.name
|
|
|
|
|
))))?;
|
2025-04-04 21:31:41 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Non-DAV namespace, not supported
|
2026-02-14 01:29:34 +01:00
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new(format!(
|
|
|
|
|
"{}:{}",
|
|
|
|
|
prop.namespace, prop.name
|
|
|
|
|
))))?;
|
2025-04-04 21:31:41 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 01:48:55 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Parse a PROPPATCH XML request
|
|
|
|
|
pub fn parse_proppatch<R: Read>(reader: R) -> Result<(Vec<PropValue>, Vec<QualifiedName>)> {
|
|
|
|
|
let mut xml_reader = Reader::from_reader(BufReader::new(reader));
|
2025-04-13 01:04:04 +02:00
|
|
|
xml_reader.config_mut().trim_text(true);
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
let mut buffer = Vec::new();
|
|
|
|
|
let mut in_propertyupdate = false;
|
|
|
|
|
let mut in_set = false;
|
|
|
|
|
let mut in_remove = false;
|
|
|
|
|
let mut in_prop = false;
|
|
|
|
|
let mut current_prop: Option<QualifiedName> = None;
|
|
|
|
|
let mut props_to_set = Vec::new();
|
|
|
|
|
let mut props_to_remove = Vec::new();
|
|
|
|
|
let mut current_text = String::new();
|
2026-03-02 20:40:41 +00:00
|
|
|
let mut ns_map = std::collections::HashMap::<String, String>::new();
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
loop {
|
|
|
|
|
match xml_reader.read_event_into(&mut buffer) {
|
|
|
|
|
Ok(Event::Start(ref e)) => {
|
2026-03-02 20:40:41 +00:00
|
|
|
Self::collect_ns_decls(e, &mut ns_map);
|
2025-04-04 21:31:41 +02:00
|
|
|
let name = e.name();
|
|
|
|
|
let name_str = std::str::from_utf8(name.as_ref()).unwrap_or("");
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
match name_str {
|
2026-02-14 01:29:34 +01:00
|
|
|
s if s == "propertyupdate" || s.ends_with(":propertyupdate") => {
|
|
|
|
|
in_propertyupdate = true
|
|
|
|
|
}
|
|
|
|
|
s if (in_propertyupdate && (s == "set" || s.ends_with(":set"))) => {
|
|
|
|
|
in_set = true
|
|
|
|
|
}
|
|
|
|
|
s if (in_propertyupdate && (s == "remove" || s.ends_with(":remove"))) => {
|
|
|
|
|
in_remove = true
|
|
|
|
|
}
|
|
|
|
|
s if ((in_set || in_remove) && (s == "prop" || s.ends_with(":prop"))) => {
|
|
|
|
|
in_prop = true
|
|
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
_ if in_prop => {
|
2026-03-02 20:40:41 +00:00
|
|
|
current_prop = Some(Self::resolve_name(name_str, &ns_map));
|
2025-04-04 21:31:41 +02:00
|
|
|
current_text.clear();
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
_ => (),
|
2025-04-04 21:31:41 +02:00
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
Ok(Event::Text(e)) => {
|
|
|
|
|
if current_prop.is_some() {
|
2026-02-02 23:56:40 +01:00
|
|
|
current_text.push_str(&e.decode().unwrap_or_default());
|
2025-04-04 21:31:41 +02:00
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
Ok(Event::End(ref e)) => {
|
|
|
|
|
let name = e.name();
|
|
|
|
|
let name_str = std::str::from_utf8(name.as_ref()).unwrap_or("");
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
match name_str {
|
2026-02-14 01:29:34 +01:00
|
|
|
s if s == "propertyupdate" || s.ends_with(":propertyupdate") => {
|
|
|
|
|
in_propertyupdate = false
|
|
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
s if s == "set" || s.ends_with(":set") => in_set = false,
|
|
|
|
|
s if s == "remove" || s.ends_with(":remove") => in_remove = false,
|
|
|
|
|
s if s == "prop" || s.ends_with(":prop") => in_prop = false,
|
|
|
|
|
_ if in_prop => {
|
|
|
|
|
// End of property element
|
|
|
|
|
if let Some(prop_name) = current_prop.take() {
|
|
|
|
|
if in_set {
|
|
|
|
|
props_to_set.push(PropValue {
|
|
|
|
|
name: prop_name,
|
2026-02-14 01:29:34 +01:00
|
|
|
value: if current_text.is_empty() {
|
|
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
Some(current_text.clone())
|
|
|
|
|
},
|
2025-04-04 21:31:41 +02:00
|
|
|
});
|
|
|
|
|
} else if in_remove {
|
|
|
|
|
props_to_remove.push(prop_name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
current_text.clear();
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
_ => (),
|
2025-04-04 21:31:41 +02:00
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
Ok(Event::Empty(ref e)) => {
|
2026-03-02 20:40:41 +00:00
|
|
|
Self::collect_ns_decls(e, &mut ns_map);
|
2025-04-04 21:31:41 +02:00
|
|
|
let name = e.name();
|
|
|
|
|
let name_str = std::str::from_utf8(name.as_ref()).unwrap_or("");
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
if in_prop {
|
2026-03-02 20:40:41 +00:00
|
|
|
let qname = Self::resolve_name(name_str, &ns_map);
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
if in_set {
|
|
|
|
|
props_to_set.push(PropValue {
|
|
|
|
|
name: qname,
|
|
|
|
|
value: None,
|
|
|
|
|
});
|
|
|
|
|
} else if in_remove {
|
|
|
|
|
props_to_remove.push(qname);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
Ok(Event::Eof) => break,
|
|
|
|
|
Err(e) => return Err(WebDavError::XmlError(e)),
|
|
|
|
|
_ => (),
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
buffer.clear();
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
Ok((props_to_set, props_to_remove))
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Generate a PROPPATCH response
|
|
|
|
|
pub fn generate_proppatch_response<W: Write>(
|
|
|
|
|
writer: W,
|
|
|
|
|
href: &str,
|
|
|
|
|
results: &[(&QualifiedName, bool)],
|
|
|
|
|
) -> Result<()> {
|
|
|
|
|
let mut xml_writer = Writer::new(writer);
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Start multistatus response
|
2026-02-14 01:29:34 +01:00
|
|
|
xml_writer.write_event(Event::Start(
|
|
|
|
|
BytesStart::new("D:multistatus").with_attributes([("xmlns:D", "DAV:")]),
|
|
|
|
|
))?;
|
|
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Start response element
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:response")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write href
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:href")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(href)))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:href")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Group results by status
|
|
|
|
|
let mut success_props = Vec::new();
|
|
|
|
|
let mut failed_props = Vec::new();
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
for (prop, success) in results {
|
|
|
|
|
if *success {
|
|
|
|
|
success_props.push(prop);
|
|
|
|
|
} else {
|
|
|
|
|
failed_props.push(prop);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write successful properties
|
|
|
|
|
if !success_props.is_empty() {
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Start prop
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:prop")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write property names
|
|
|
|
|
for prop in success_props {
|
|
|
|
|
let prop_name = if prop.namespace == "DAV:" {
|
|
|
|
|
format!("D:{}", prop.name)
|
|
|
|
|
} else {
|
|
|
|
|
format!("{}:{}", prop.namespace, prop.name)
|
|
|
|
|
};
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new(&prop_name)))?;
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// End prop
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:prop")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write status
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:status")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new("HTTP/1.1 200 OK")))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:status")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// End propstat
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:propstat")))?;
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write failed properties
|
|
|
|
|
if !failed_props.is_empty() {
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:propstat")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Start prop
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:prop")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write property names
|
|
|
|
|
for prop in failed_props {
|
|
|
|
|
let prop_name = if prop.namespace == "DAV:" {
|
|
|
|
|
format!("D:{}", prop.name)
|
|
|
|
|
} else {
|
|
|
|
|
format!("{}:{}", prop.namespace, prop.name)
|
|
|
|
|
};
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new(&prop_name)))?;
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// End prop
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:prop")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write status
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:status")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new("HTTP/1.1 403 Forbidden")))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:status")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// End propstat
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:propstat")))?;
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// End response
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:response")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// End multistatus
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:multistatus")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Parse a LOCK XML request
|
|
|
|
|
pub fn parse_lockinfo<R: Read>(reader: R) -> Result<(LockScope, LockType, Option<String>)> {
|
|
|
|
|
let mut xml_reader = Reader::from_reader(BufReader::new(reader));
|
2025-04-13 01:04:04 +02:00
|
|
|
xml_reader.config_mut().trim_text(true);
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
let mut buffer = Vec::new();
|
|
|
|
|
let mut in_lockinfo = false;
|
|
|
|
|
let mut in_lockscope = false;
|
|
|
|
|
let mut in_locktype = false;
|
|
|
|
|
let mut in_owner = false;
|
|
|
|
|
let mut owner_text = String::new();
|
|
|
|
|
let mut scope = LockScope::Exclusive; // Default to exclusive
|
2026-02-14 01:29:34 +01:00
|
|
|
let mut type_ = LockType::Write; // Default to write (only supported type)
|
|
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
loop {
|
|
|
|
|
match xml_reader.read_event_into(&mut buffer) {
|
|
|
|
|
Ok(Event::Start(ref e)) => {
|
|
|
|
|
let name = e.name();
|
|
|
|
|
let name_str = std::str::from_utf8(name.as_ref()).unwrap_or("");
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
match name_str {
|
|
|
|
|
s if s == "lockinfo" || s.ends_with(":lockinfo") => in_lockinfo = true,
|
2026-02-14 01:29:34 +01:00
|
|
|
s if in_lockinfo && (s == "lockscope" || s.ends_with(":lockscope")) => {
|
|
|
|
|
in_lockscope = true
|
|
|
|
|
}
|
|
|
|
|
s if in_lockinfo && (s == "locktype" || s.ends_with(":locktype")) => {
|
|
|
|
|
in_locktype = true
|
|
|
|
|
}
|
|
|
|
|
s if in_lockinfo && (s == "owner" || s.ends_with(":owner")) => {
|
|
|
|
|
in_owner = true
|
|
|
|
|
}
|
|
|
|
|
s if in_lockscope && (s == "exclusive" || s.ends_with(":exclusive")) => {
|
|
|
|
|
scope = LockScope::Exclusive
|
|
|
|
|
}
|
|
|
|
|
s if in_lockscope && (s == "shared" || s.ends_with(":shared")) => {
|
|
|
|
|
scope = LockScope::Shared
|
|
|
|
|
}
|
|
|
|
|
s if in_locktype && (s == "write" || s.ends_with(":write")) => {
|
|
|
|
|
type_ = LockType::Write
|
|
|
|
|
}
|
|
|
|
|
_ => (),
|
2025-04-04 21:31:41 +02:00
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
Ok(Event::Text(e)) => {
|
|
|
|
|
if in_owner {
|
2026-02-02 23:56:40 +01:00
|
|
|
owner_text.push_str(&e.decode().unwrap_or_default());
|
2025-04-04 21:31:41 +02:00
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
Ok(Event::End(ref e)) => {
|
|
|
|
|
let name = e.name();
|
|
|
|
|
let name_str = std::str::from_utf8(name.as_ref()).unwrap_or("");
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
match name_str {
|
|
|
|
|
s if s == "lockinfo" || s.ends_with(":lockinfo") => in_lockinfo = false,
|
|
|
|
|
s if s == "lockscope" || s.ends_with(":lockscope") => in_lockscope = false,
|
|
|
|
|
s if s == "locktype" || s.ends_with(":locktype") => in_locktype = false,
|
|
|
|
|
s if s == "owner" || s.ends_with(":owner") => in_owner = false,
|
2026-02-14 01:29:34 +01:00
|
|
|
_ => (),
|
2025-04-04 21:31:41 +02:00
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
Ok(Event::Empty(ref e)) => {
|
|
|
|
|
let name = e.name();
|
|
|
|
|
let name_str = std::str::from_utf8(name.as_ref()).unwrap_or("");
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
match name_str {
|
2026-02-14 01:29:34 +01:00
|
|
|
s if in_lockscope && (s == "exclusive" || s.ends_with(":exclusive")) => {
|
|
|
|
|
scope = LockScope::Exclusive
|
|
|
|
|
}
|
|
|
|
|
s if in_lockscope && (s == "shared" || s.ends_with(":shared")) => {
|
|
|
|
|
scope = LockScope::Shared
|
|
|
|
|
}
|
|
|
|
|
s if in_locktype && (s == "write" || s.ends_with(":write")) => {
|
|
|
|
|
type_ = LockType::Write
|
|
|
|
|
}
|
|
|
|
|
_ => (),
|
2025-04-04 21:31:41 +02:00
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
Ok(Event::Eof) => break,
|
|
|
|
|
Err(e) => return Err(WebDavError::XmlError(e)),
|
|
|
|
|
_ => (),
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
buffer.clear();
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
|
|
|
|
let owner = if owner_text.is_empty() {
|
|
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
Some(owner_text)
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
Ok((scope, type_, owner))
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Generate a LOCK response (lockdiscovery)
|
|
|
|
|
pub fn generate_lock_response<W: Write>(
|
|
|
|
|
writer: W,
|
|
|
|
|
lock_info: &LockInfo,
|
|
|
|
|
href: &str,
|
|
|
|
|
) -> Result<()> {
|
|
|
|
|
let mut xml_writer = Writer::new(writer);
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Start prop element (direct response, not multistatus)
|
2026-02-14 01:29:34 +01:00
|
|
|
xml_writer.write_event(Event::Start(
|
|
|
|
|
BytesStart::new("D:prop").with_attributes([("xmlns:D", "DAV:")]),
|
|
|
|
|
))?;
|
|
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Start lockdiscovery
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:lockdiscovery")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Start activelock
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:activelock")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write locktype
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:locktype")))?;
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:write")))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:locktype")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write lockscope
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:lockscope")))?;
|
|
|
|
|
match lock_info.scope {
|
|
|
|
|
LockScope::Exclusive => {
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:exclusive")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
LockScope::Shared => {
|
|
|
|
|
xml_writer.write_event(Event::Empty(BytesStart::new("D:shared")))?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:lockscope")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write depth
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:depth")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&lock_info.depth)))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:depth")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write owner (if provided)
|
|
|
|
|
if let Some(owner) = &lock_info.owner {
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:owner")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(owner)))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:owner")))?;
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write timeout (if provided)
|
|
|
|
|
if let Some(timeout) = &lock_info.timeout {
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:timeout")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(timeout)))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:timeout")))?;
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write locktoken
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:locktoken")))?;
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:href")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(&lock_info.token)))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:href")))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:locktoken")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// Write lockroot
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:lockroot")))?;
|
|
|
|
|
xml_writer.write_event(Event::Start(BytesStart::new("D:href")))?;
|
|
|
|
|
xml_writer.write_event(Event::Text(BytesText::new(href)))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:href")))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:lockroot")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
// End activelock, lockdiscovery, and prop
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:activelock")))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:lockdiscovery")))?;
|
|
|
|
|
xml_writer.write_event(Event::End(BytesEnd::new("D:prop")))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 01:48:55 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Helper method to extract namespace from tag name
|
2025-04-13 01:04:04 +02:00
|
|
|
pub fn extract_namespace(name: &str) -> String {
|
2026-02-14 01:26:02 +01:00
|
|
|
if let Some(idx) = name.rfind(':')
|
2026-02-14 01:29:34 +01:00
|
|
|
&& idx > 0
|
|
|
|
|
{
|
|
|
|
|
return name[..idx].to_string();
|
|
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
// Default namespace for WebDAV
|
|
|
|
|
"DAV:".to_string()
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-04 21:31:41 +02:00
|
|
|
/// Helper method to extract local name from tag name
|
2025-04-13 01:04:04 +02:00
|
|
|
pub fn extract_local_name(name: &str) -> String {
|
2026-02-14 01:26:02 +01:00
|
|
|
if let Some(idx) = name.rfind(':')
|
2026-02-14 01:29:34 +01:00
|
|
|
&& idx > 0
|
|
|
|
|
&& idx < name.len() - 1
|
|
|
|
|
{
|
|
|
|
|
return name[idx + 1..].to_string();
|
|
|
|
|
}
|
2025-04-04 21:31:41 +02:00
|
|
|
name.to_string()
|
|
|
|
|
}
|
2026-02-24 15:11:56 +01:00
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
|
// Streaming PROPFIND helpers
|
|
|
|
|
//
|
|
|
|
|
// These methods write incremental XML fragments so the caller
|
|
|
|
|
// can flush chunks to the HTTP body without buffering the whole
|
|
|
|
|
// response in memory.
|
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// Writes the opening `<D:multistatus>` tag.
|
|
|
|
|
pub fn write_multistatus_start<W: Write>(writer: &mut Writer<W>) -> Result<()> {
|
|
|
|
|
writer.write_event(Event::Start(
|
|
|
|
|
BytesStart::new("D:multistatus").with_attributes([("xmlns:D", "DAV:")]),
|
|
|
|
|
))?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Writes the closing `</D:multistatus>` tag.
|
|
|
|
|
pub fn write_multistatus_end<W: Write>(writer: &mut Writer<W>) -> Result<()> {
|
|
|
|
|
writer.write_event(Event::End(BytesEnd::new("D:multistatus")))?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Writes a single `<D:response>` element for a folder.
|
|
|
|
|
pub fn write_folder_entry<W: Write>(
|
|
|
|
|
writer: &mut Writer<W>,
|
|
|
|
|
folder: &FolderDto,
|
|
|
|
|
request: &PropFindRequest,
|
|
|
|
|
href: &str,
|
|
|
|
|
) -> Result<()> {
|
|
|
|
|
Self::write_folder_response(writer, folder, request, href)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Writes a single `<D:response>` element for a file.
|
|
|
|
|
pub fn write_file_entry<W: Write>(
|
|
|
|
|
writer: &mut Writer<W>,
|
|
|
|
|
file: &FileDto,
|
|
|
|
|
request: &PropFindRequest,
|
|
|
|
|
href: &str,
|
|
|
|
|
) -> Result<()> {
|
|
|
|
|
Self::write_file_response(writer, file, request, href)
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|