Skip to main content

aster_forge_webdav/
request.rs

1//! Transport-neutral WebDAV request head parsing.
2
3use http::{HeaderMap, Method, Uri};
4
5use crate::DavPath;
6use crate::event::DavOperation;
7use crate::protocol::{
8    DavProtocolError, Depth, Destination, IfHeader, destination_relative_path, parse_copy_depth,
9    parse_delete_depth, parse_if_header, parse_lock_depth, parse_move_depth, parse_overwrite,
10    parse_propfind_depth, strip_mount_prefix,
11};
12
13/// WebDAV method recognized by the protocol layer.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum DavMethod {
16    Options,
17    Propfind,
18    Proppatch,
19    Get,
20    Head,
21    Put,
22    Mkcol,
23    Delete,
24    Copy,
25    Move,
26    Lock,
27    Unlock,
28    Report,
29    VersionControl,
30}
31
32/// How the transport adapter must handle a request body before product code runs.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum DavBodyPolicy {
35    /// Reject the first non-empty body chunk.
36    Empty,
37    /// Collect the body up to the product-supplied XML limit.
38    BoundedXml,
39    /// Leave the body as a stream for the product storage adapter.
40    Stream,
41    /// Preserve the existing method behavior without consuming the body.
42    Unused,
43}
44
45impl DavMethod {
46    /// Parses a supported HTTP/WebDAV method.
47    #[must_use]
48    pub fn from_method(method: &Method) -> Option<Self> {
49        Self::from_name(method.as_str())
50    }
51
52    /// Parses a supported HTTP/WebDAV method name across transport implementations.
53    #[must_use]
54    pub fn from_name(method: &str) -> Option<Self> {
55        match method {
56            "OPTIONS" => Some(Self::Options),
57            "PROPFIND" => Some(Self::Propfind),
58            "PROPPATCH" => Some(Self::Proppatch),
59            "GET" => Some(Self::Get),
60            "HEAD" => Some(Self::Head),
61            "PUT" => Some(Self::Put),
62            "MKCOL" => Some(Self::Mkcol),
63            "DELETE" => Some(Self::Delete),
64            "COPY" => Some(Self::Copy),
65            "MOVE" => Some(Self::Move),
66            "LOCK" => Some(Self::Lock),
67            "UNLOCK" => Some(Self::Unlock),
68            "REPORT" => Some(Self::Report),
69            "VERSION-CONTROL" => Some(Self::VersionControl),
70            _ => None,
71        }
72    }
73
74    /// Returns the corresponding observable operation.
75    #[must_use]
76    pub const fn operation(self) -> DavOperation {
77        match self {
78            Self::Options => DavOperation::Options,
79            Self::Propfind => DavOperation::Propfind,
80            Self::Proppatch => DavOperation::Proppatch,
81            Self::Get => DavOperation::Get,
82            Self::Head => DavOperation::Head,
83            Self::Put => DavOperation::Put,
84            Self::Mkcol => DavOperation::Mkcol,
85            Self::Delete => DavOperation::Delete,
86            Self::Copy => DavOperation::Copy,
87            Self::Move => DavOperation::Move,
88            Self::Lock => DavOperation::Lock,
89            Self::Unlock => DavOperation::Unlock,
90            Self::Report => DavOperation::Report,
91            Self::VersionControl => DavOperation::VersionControl,
92        }
93    }
94
95    /// Returns the body handling contract for this method.
96    #[must_use]
97    pub const fn body_policy(self) -> DavBodyPolicy {
98        match self {
99            Self::Options | Self::Mkcol | Self::Delete | Self::Copy | Self::Move | Self::Unlock => {
100                DavBodyPolicy::Empty
101            }
102            Self::Propfind | Self::Proppatch | Self::Lock | Self::Report => {
103                DavBodyPolicy::BoundedXml
104            }
105            Self::Put => DavBodyPolicy::Stream,
106            Self::Get | Self::Head | Self::VersionControl => DavBodyPolicy::Unused,
107        }
108    }
109}
110
111/// Request origin needed for same-origin tagged URI and destination validation.
112#[derive(Debug, Clone, PartialEq, Eq)]
113pub struct DavRequestOrigin {
114    pub scheme: String,
115    pub host: String,
116}
117
118/// Parsed, body-independent WebDAV request data.
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct DavRequestHead {
121    pub method: DavMethod,
122    pub target: DavPath,
123    pub origin: DavRequestOrigin,
124    pub depth: Option<Depth>,
125    pub overwrite: Option<bool>,
126    pub destination: Option<Destination>,
127    pub if_header: Option<IfHeader>,
128}
129
130impl DavRequestHead {
131    /// Parses protocol headers and the mount-relative target before product code is called.
132    pub fn parse(
133        method: DavMethod,
134        uri: &Uri,
135        headers: &HeaderMap,
136        mount_path: &str,
137        origin: &DavRequestOrigin,
138    ) -> Result<Self, DavProtocolError> {
139        let relative = strip_mount_prefix(uri.path(), mount_path).ok_or_else(|| {
140            DavProtocolError::bad_request("Request target must stay under WebDAV prefix")
141        })?;
142        let target = DavPath::new(relative)
143            .map_err(|_| DavProtocolError::bad_request("Invalid request path"))?;
144
145        let depth = match method {
146            DavMethod::Propfind => Some(parse_propfind_depth(headers)?),
147            DavMethod::Copy => Some(parse_copy_depth(headers)?),
148            DavMethod::Move => Some(parse_move_depth(headers)?),
149            DavMethod::Delete => Some(parse_delete_depth(headers)?),
150            DavMethod::Lock => Some(parse_lock_depth(headers)?),
151            _ => None,
152        };
153        let (overwrite, destination) = match method {
154            DavMethod::Copy | DavMethod::Move => (
155                Some(parse_overwrite(headers)?),
156                Some(destination_relative_path(
157                    headers,
158                    mount_path,
159                    &origin.scheme,
160                    &origin.host,
161                )?),
162            ),
163            _ => (None, None),
164        };
165
166        Ok(Self {
167            method,
168            target,
169            origin: origin.clone(),
170            depth,
171            overwrite,
172            destination,
173            if_header: parse_if_header(headers)?,
174        })
175    }
176}