From 32225bdda80b0d0c12d48f4db1f3087acceeb786 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Tue, 10 Jun 2025 17:42:03 +0200 Subject: [PATCH] Implement nonfunctional COPY and MOVE method Fixes #69 for now --- crates/dav/src/header/depth.rs | 21 ++++++++++++++++++++- crates/dav/src/header/overwrite.rs | 25 +++++++++++++++++++++++++ crates/dav/src/resource/axum_methods.rs | 18 ++---------------- crates/dav/src/resource/axum_service.rs | 22 +++++++++++----------- crates/dav/src/resource/methods/copy.rs | 25 +++++++++++++++++++++++++ crates/dav/src/resource/methods/mod.rs | 4 ++++ crates/dav/src/resource/methods/mv.rs | 25 +++++++++++++++++++++++++ 7 files changed, 112 insertions(+), 28 deletions(-) create mode 100644 crates/dav/src/resource/methods/copy.rs create mode 100644 crates/dav/src/resource/methods/mv.rs diff --git a/crates/dav/src/header/depth.rs b/crates/dav/src/header/depth.rs index 3ece458..21592c6 100644 --- a/crates/dav/src/header/depth.rs +++ b/crates/dav/src/header/depth.rs @@ -1,4 +1,8 @@ -use axum::{body::Body, extract::FromRequestParts, response::IntoResponse}; +use axum::{ + body::Body, + extract::{FromRequestParts, OptionalFromRequestParts}, + response::IntoResponse, +}; use rustical_xml::{ValueDeserialize, ValueSerialize, XmlError}; use thiserror::Error; @@ -59,6 +63,21 @@ impl TryFrom<&[u8]> for Depth { } } +impl OptionalFromRequestParts for Depth { + type Rejection = InvalidDepthHeader; + + async fn from_request_parts( + parts: &mut axum::http::request::Parts, + _state: &S, + ) -> Result, Self::Rejection> { + if let Some(depth_header) = parts.headers.get("Depth") { + Ok(Some(depth_header.as_bytes().try_into()?)) + } else { + Ok(None) + } + } +} + impl FromRequestParts for Depth { type Rejection = InvalidDepthHeader; diff --git a/crates/dav/src/header/overwrite.rs b/crates/dav/src/header/overwrite.rs index 2aade9d..29ae8d8 100644 --- a/crates/dav/src/header/overwrite.rs +++ b/crates/dav/src/header/overwrite.rs @@ -1,9 +1,19 @@ +use axum::{body::Body, extract::FromRequestParts, response::IntoResponse}; use thiserror::Error; #[derive(Error, Debug)] #[error("Invalid Overwrite header")] pub struct InvalidOverwriteHeader; +impl IntoResponse for InvalidOverwriteHeader { + fn into_response(self) -> axum::response::Response { + axum::response::Response::builder() + .status(axum::http::StatusCode::BAD_REQUEST) + .body(Body::new("Invalid Overwrite header".to_string())) + .expect("this always works") + } +} + #[derive(Debug, PartialEq, Default)] pub enum Overwrite { #[default] @@ -17,6 +27,21 @@ impl Overwrite { } } +impl FromRequestParts for Overwrite { + type Rejection = InvalidOverwriteHeader; + + async fn from_request_parts( + parts: &mut axum::http::request::Parts, + _state: &S, + ) -> Result { + if let Some(overwrite_header) = parts.headers.get("Overwrite") { + overwrite_header.as_bytes().try_into() + } else { + Ok(Self::default()) + } + } +} + impl TryFrom<&[u8]> for Overwrite { type Error = InvalidOverwriteHeader; diff --git a/crates/dav/src/resource/axum_methods.rs b/crates/dav/src/resource/axum_methods.rs index 41b62a1..6179726 100644 --- a/crates/dav/src/resource/axum_methods.rs +++ b/crates/dav/src/resource/axum_methods.rs @@ -38,16 +38,6 @@ pub trait AxumMethods: Sized + Send + Sync + 'static { None } - #[inline] - fn copy() -> Option> { - None - } - - #[inline] - fn mv() -> Option> { - None - } - #[inline] fn put() -> Option> { None @@ -58,6 +48,8 @@ pub trait AxumMethods: Sized + Send + Sync + 'static { let mut allow = vec![ Method::from_str("PROPFIND").unwrap(), Method::from_str("PROPPATCH").unwrap(), + Method::from_str("COPY").unwrap(), + Method::from_str("MOVE").unwrap(), Method::DELETE, Method::OPTIONS, ]; @@ -79,12 +71,6 @@ pub trait AxumMethods: Sized + Send + Sync + 'static { if Self::mkcalendar().is_some() { allow.push(Method::from_str("MKCALENDAR").unwrap()); } - if Self::copy().is_some() { - allow.push(Method::from_str("COPY").unwrap()); - } - if Self::mv().is_some() { - allow.push(Method::from_str("MOVE").unwrap()); - } if Self::put().is_some() { allow.push(Method::PUT); } diff --git a/crates/dav/src/resource/axum_service.rs b/crates/dav/src/resource/axum_service.rs index 286f285..22dcf0e 100644 --- a/crates/dav/src/resource/axum_service.rs +++ b/crates/dav/src/resource/axum_service.rs @@ -1,5 +1,9 @@ use super::methods::{axum_route_propfind, axum_route_proppatch}; -use crate::resource::{ResourceService, axum_methods::AxumMethods}; +use crate::resource::{ + ResourceService, + axum_methods::AxumMethods, + methods::{axum_route_copy, axum_route_move}, +}; use axum::{ body::Body, extract::FromRequestParts, @@ -51,12 +55,18 @@ where Handler::with_state(axum_route_proppatch::, self.resource_service.clone()); let mut delete_service = Handler::with_state(axum_route_delete::, self.resource_service.clone()); + let mut move_service = + Handler::with_state(axum_route_move::, self.resource_service.clone()); + let mut copy_service = + Handler::with_state(axum_route_copy::, self.resource_service.clone()); let mut options_service = Handler::with_state(route_options::, ()); match req.method().as_str() { "PROPFIND" => return Box::pin(Service::call(&mut propfind_service, req)), "PROPPATCH" => return Box::pin(Service::call(&mut proppatch_service, req)), "DELETE" => return Box::pin(Service::call(&mut delete_service, req)), "OPTIONS" => return Box::pin(Service::call(&mut options_service, req)), + "MOVE" => return Box::pin(Service::call(&mut move_service, req)), + "COPY" => return Box::pin(Service::call(&mut copy_service, req)), "REPORT" => { if let Some(svc) = RS::report() { return svc(self.resource_service.clone(), req); @@ -87,16 +97,6 @@ where return svc(self.resource_service.clone(), req); } } - "COPY" => { - if let Some(svc) = RS::copy() { - return svc(self.resource_service.clone(), req); - } - } - "MOVE" => { - if let Some(svc) = RS::mv() { - return svc(self.resource_service.clone(), req); - } - } "PUT" => { if let Some(svc) = RS::put() { return svc(self.resource_service.clone(), req); diff --git a/crates/dav/src/resource/methods/copy.rs b/crates/dav/src/resource/methods/copy.rs new file mode 100644 index 0000000..6416401 --- /dev/null +++ b/crates/dav/src/resource/methods/copy.rs @@ -0,0 +1,25 @@ +use axum::{ + extract::{Path, State}, + response::{IntoResponse, Response}, +}; +use http::StatusCode; +use tracing::instrument; + +use crate::{ + header::{Depth, Overwrite}, + resource::ResourceService, +}; + +#[instrument(skip(_path, _resource_service,))] +pub(crate) async fn axum_route_copy( + Path(_path): Path, + State(_resource_service): State, + depth: Option, + principal: R::Principal, + overwrite: Overwrite, +) -> Result { + // TODO: Actually implement, but to be WebDAV-compliant we must at least support this route but + // can return a 403 error + let _depth = depth.unwrap_or(Depth::Infinity); + Ok(StatusCode::FORBIDDEN.into_response()) +} diff --git a/crates/dav/src/resource/methods/mod.rs b/crates/dav/src/resource/methods/mod.rs index 84eec52..a8610a3 100644 --- a/crates/dav/src/resource/methods/mod.rs +++ b/crates/dav/src/resource/methods/mod.rs @@ -1,7 +1,11 @@ +mod copy; mod delete; +mod mv; mod propfind; mod proppatch; +pub(crate) use copy::axum_route_copy; pub(crate) use delete::axum_route_delete; +pub(crate) use mv::axum_route_move; pub(crate) use propfind::axum_route_propfind; pub(crate) use proppatch::axum_route_proppatch; diff --git a/crates/dav/src/resource/methods/mv.rs b/crates/dav/src/resource/methods/mv.rs new file mode 100644 index 0000000..00fc054 --- /dev/null +++ b/crates/dav/src/resource/methods/mv.rs @@ -0,0 +1,25 @@ +use axum::{ + extract::{Path, State}, + response::{IntoResponse, Response}, +}; +use http::StatusCode; +use tracing::instrument; + +use crate::{ + header::{Depth, Overwrite}, + resource::ResourceService, +}; + +#[instrument(skip(_path, _resource_service,))] +pub(crate) async fn axum_route_move( + Path(_path): Path, + State(_resource_service): State, + depth: Option, + principal: R::Principal, + overwrite: Overwrite, +) -> Result { + // TODO: Actually implement, but to be WebDAV-compliant we must at least support this route but + // can return a 403 error + let _depth = depth.unwrap_or(Depth::Infinity); + Ok(StatusCode::FORBIDDEN.into_response()) +}