Skip to main content

aster_forge_webdav/
deltav.rs

1//! Product-neutral DeltaV request planning and response composition.
2
3use http::header::{CACHE_CONTROL, CONTENT_TYPE};
4use http::{HeaderValue, StatusCode};
5
6use crate::{
7    DavErrorCondition, DavResourceKind, DavResponse, DavVersionXml, DavXmlError, dav_error_element,
8    dav_version_multistatus_element, parse_report_root,
9};
10
11/// Failure while selecting the supported DeltaV REPORT grammar.
12#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
13pub enum DavVersionTreeReportError {
14    /// The REPORT body is not safe, well-formed WebDAV XML.
15    #[error(transparent)]
16    Xml(#[from] DavXmlError),
17    /// The REPORT root is not the supported `DAV:version-tree` report.
18    #[error("unsupported DeltaV REPORT type: {name}")]
19    Unsupported { name: String },
20}
21
22/// Validates that a REPORT request selects `DAV:version-tree`.
23pub fn validate_version_tree_report(body: &[u8]) -> Result<(), DavVersionTreeReportError> {
24    let root = parse_report_root(body)?;
25    if root.name == "version-tree" && root.namespace.as_deref() == Some("DAV:") {
26        Ok(())
27    } else {
28        Err(DavVersionTreeReportError::Unsupported { name: root.name })
29    }
30}
31
32/// Builds the protocol response for a REPORT grammar selection failure.
33pub fn version_tree_report_error_response(
34    error: &DavVersionTreeReportError,
35) -> Result<DavResponse, DavXmlError> {
36    match error {
37        DavVersionTreeReportError::Xml(DavXmlError::ExternalEntity) => xml_response(
38            StatusCode::FORBIDDEN,
39            dav_error_element(&DavErrorCondition::NoExternalEntities),
40        ),
41        DavVersionTreeReportError::Xml(DavXmlError::TooLarge) => Ok(text_response(
42            StatusCode::PAYLOAD_TOO_LARGE,
43            "WebDAV XML body too large",
44        )),
45        DavVersionTreeReportError::Xml(
46            DavXmlError::TooDeep | DavXmlError::Malformed | DavXmlError::InvalidGrammar,
47        ) => Ok(text_response(StatusCode::BAD_REQUEST, "Invalid XML body")),
48        DavVersionTreeReportError::Unsupported { name } => Ok(text_response(
49            StatusCode::NOT_IMPLEMENTED,
50            format!("Unsupported REPORT type: {name}"),
51        )),
52    }
53}
54
55/// Builds the file-only conflict response for a version-tree REPORT.
56#[must_use]
57pub fn version_tree_non_file_response() -> DavResponse {
58    text_response(
59        StatusCode::CONFLICT,
60        "Version history is only available for files",
61    )
62}
63
64/// Builds a complete 207 DeltaV version-tree response.
65pub fn version_tree_response(versions: Vec<DavVersionXml>) -> Result<DavResponse, DavXmlError> {
66    xml_response(
67        StatusCode::MULTI_STATUS,
68        dav_version_multistatus_element(versions),
69    )
70}
71
72/// Selects the VERSION-CONTROL response for the resolved resource kind.
73#[must_use]
74pub fn version_control_response(kind: DavResourceKind) -> DavResponse {
75    match kind {
76        DavResourceKind::File => text_response(StatusCode::OK, "Already under version control"),
77        DavResourceKind::Collection => text_response(
78            StatusCode::METHOD_NOT_ALLOWED,
79            "Only files support version control",
80        ),
81    }
82}
83
84fn text_response(status: StatusCode, body: impl Into<String>) -> DavResponse {
85    let mut response = DavResponse::bytes(status, body.into());
86    response.headers.insert(
87        CONTENT_TYPE,
88        HeaderValue::from_static("text/plain; charset=utf-8"),
89    );
90    if status.is_client_error() || status.is_server_error() {
91        response
92            .headers
93            .insert(CACHE_CONTROL, HeaderValue::from_static("no-store"));
94    }
95    response
96}
97
98fn xml_response(
99    status: StatusCode,
100    root: crate::DavXmlElement,
101) -> Result<DavResponse, DavXmlError> {
102    let mut response = DavResponse::bytes(status, root.to_bytes()?);
103    response.headers.insert(
104        CONTENT_TYPE,
105        HeaderValue::from_static("application/xml; charset=utf-8"),
106    );
107    if status.is_client_error() || status.is_server_error() {
108        response
109            .headers
110            .insert(CACHE_CONTROL, HeaderValue::from_static("no-store"));
111    }
112    Ok(response)
113}