mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 14:42:30 +00:00
@@ -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 rustical_xml::{ValueDeserialize, ValueSerialize, XmlError};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
@@ -59,6 +63,21 @@ impl TryFrom<&[u8]> for Depth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S: Send + Sync> OptionalFromRequestParts<S> for Depth {
|
||||||
|
type Rejection = InvalidDepthHeader;
|
||||||
|
|
||||||
|
async fn from_request_parts(
|
||||||
|
parts: &mut axum::http::request::Parts,
|
||||||
|
_state: &S,
|
||||||
|
) -> Result<Option<Self>, Self::Rejection> {
|
||||||
|
if let Some(depth_header) = parts.headers.get("Depth") {
|
||||||
|
Ok(Some(depth_header.as_bytes().try_into()?))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<S: Send + Sync> FromRequestParts<S> for Depth {
|
impl<S: Send + Sync> FromRequestParts<S> for Depth {
|
||||||
type Rejection = InvalidDepthHeader;
|
type Rejection = InvalidDepthHeader;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
|
use axum::{body::Body, extract::FromRequestParts, response::IntoResponse};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
#[error("Invalid Overwrite header")]
|
#[error("Invalid Overwrite header")]
|
||||||
pub struct InvalidOverwriteHeader;
|
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)]
|
#[derive(Debug, PartialEq, Default)]
|
||||||
pub enum Overwrite {
|
pub enum Overwrite {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -17,6 +27,21 @@ impl Overwrite {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S: Send + Sync> FromRequestParts<S> for Overwrite {
|
||||||
|
type Rejection = InvalidOverwriteHeader;
|
||||||
|
|
||||||
|
async fn from_request_parts(
|
||||||
|
parts: &mut axum::http::request::Parts,
|
||||||
|
_state: &S,
|
||||||
|
) -> Result<Self, Self::Rejection> {
|
||||||
|
if let Some(overwrite_header) = parts.headers.get("Overwrite") {
|
||||||
|
overwrite_header.as_bytes().try_into()
|
||||||
|
} else {
|
||||||
|
Ok(Self::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<&[u8]> for Overwrite {
|
impl TryFrom<&[u8]> for Overwrite {
|
||||||
type Error = InvalidOverwriteHeader;
|
type Error = InvalidOverwriteHeader;
|
||||||
|
|
||||||
|
|||||||
@@ -38,16 +38,6 @@ pub trait AxumMethods: Sized + Send + Sync + 'static {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn copy() -> Option<MethodFunction<Self>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn mv() -> Option<MethodFunction<Self>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn put() -> Option<MethodFunction<Self>> {
|
fn put() -> Option<MethodFunction<Self>> {
|
||||||
None
|
None
|
||||||
@@ -58,6 +48,8 @@ pub trait AxumMethods: Sized + Send + Sync + 'static {
|
|||||||
let mut allow = vec![
|
let mut allow = vec![
|
||||||
Method::from_str("PROPFIND").unwrap(),
|
Method::from_str("PROPFIND").unwrap(),
|
||||||
Method::from_str("PROPPATCH").unwrap(),
|
Method::from_str("PROPPATCH").unwrap(),
|
||||||
|
Method::from_str("COPY").unwrap(),
|
||||||
|
Method::from_str("MOVE").unwrap(),
|
||||||
Method::DELETE,
|
Method::DELETE,
|
||||||
Method::OPTIONS,
|
Method::OPTIONS,
|
||||||
];
|
];
|
||||||
@@ -79,12 +71,6 @@ pub trait AxumMethods: Sized + Send + Sync + 'static {
|
|||||||
if Self::mkcalendar().is_some() {
|
if Self::mkcalendar().is_some() {
|
||||||
allow.push(Method::from_str("MKCALENDAR").unwrap());
|
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() {
|
if Self::put().is_some() {
|
||||||
allow.push(Method::PUT);
|
allow.push(Method::PUT);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
use super::methods::{axum_route_propfind, axum_route_proppatch};
|
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::{
|
use axum::{
|
||||||
body::Body,
|
body::Body,
|
||||||
extract::FromRequestParts,
|
extract::FromRequestParts,
|
||||||
@@ -51,12 +55,18 @@ where
|
|||||||
Handler::with_state(axum_route_proppatch::<RS>, self.resource_service.clone());
|
Handler::with_state(axum_route_proppatch::<RS>, self.resource_service.clone());
|
||||||
let mut delete_service =
|
let mut delete_service =
|
||||||
Handler::with_state(axum_route_delete::<RS>, self.resource_service.clone());
|
Handler::with_state(axum_route_delete::<RS>, self.resource_service.clone());
|
||||||
|
let mut move_service =
|
||||||
|
Handler::with_state(axum_route_move::<RS>, self.resource_service.clone());
|
||||||
|
let mut copy_service =
|
||||||
|
Handler::with_state(axum_route_copy::<RS>, self.resource_service.clone());
|
||||||
let mut options_service = Handler::with_state(route_options::<RS>, ());
|
let mut options_service = Handler::with_state(route_options::<RS>, ());
|
||||||
match req.method().as_str() {
|
match req.method().as_str() {
|
||||||
"PROPFIND" => return Box::pin(Service::call(&mut propfind_service, req)),
|
"PROPFIND" => return Box::pin(Service::call(&mut propfind_service, req)),
|
||||||
"PROPPATCH" => return Box::pin(Service::call(&mut proppatch_service, req)),
|
"PROPPATCH" => return Box::pin(Service::call(&mut proppatch_service, req)),
|
||||||
"DELETE" => return Box::pin(Service::call(&mut delete_service, req)),
|
"DELETE" => return Box::pin(Service::call(&mut delete_service, req)),
|
||||||
"OPTIONS" => return Box::pin(Service::call(&mut options_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" => {
|
"REPORT" => {
|
||||||
if let Some(svc) = RS::report() {
|
if let Some(svc) = RS::report() {
|
||||||
return svc(self.resource_service.clone(), req);
|
return svc(self.resource_service.clone(), req);
|
||||||
@@ -87,16 +97,6 @@ where
|
|||||||
return svc(self.resource_service.clone(), req);
|
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" => {
|
"PUT" => {
|
||||||
if let Some(svc) = RS::put() {
|
if let Some(svc) = RS::put() {
|
||||||
return svc(self.resource_service.clone(), req);
|
return svc(self.resource_service.clone(), req);
|
||||||
|
|||||||
25
crates/dav/src/resource/methods/copy.rs
Normal file
25
crates/dav/src/resource/methods/copy.rs
Normal file
@@ -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<R: ResourceService>(
|
||||||
|
Path(_path): Path<R::PathComponents>,
|
||||||
|
State(_resource_service): State<R>,
|
||||||
|
depth: Option<Depth>,
|
||||||
|
principal: R::Principal,
|
||||||
|
overwrite: Overwrite,
|
||||||
|
) -> Result<Response, R::Error> {
|
||||||
|
// 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())
|
||||||
|
}
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
|
mod copy;
|
||||||
mod delete;
|
mod delete;
|
||||||
|
mod mv;
|
||||||
mod propfind;
|
mod propfind;
|
||||||
mod proppatch;
|
mod proppatch;
|
||||||
|
|
||||||
|
pub(crate) use copy::axum_route_copy;
|
||||||
pub(crate) use delete::axum_route_delete;
|
pub(crate) use delete::axum_route_delete;
|
||||||
|
pub(crate) use mv::axum_route_move;
|
||||||
pub(crate) use propfind::axum_route_propfind;
|
pub(crate) use propfind::axum_route_propfind;
|
||||||
pub(crate) use proppatch::axum_route_proppatch;
|
pub(crate) use proppatch::axum_route_proppatch;
|
||||||
|
|||||||
25
crates/dav/src/resource/methods/mv.rs
Normal file
25
crates/dav/src/resource/methods/mv.rs
Normal file
@@ -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<R: ResourceService>(
|
||||||
|
Path(_path): Path<R::PathComponents>,
|
||||||
|
State(_resource_service): State<R>,
|
||||||
|
depth: Option<Depth>,
|
||||||
|
principal: R::Principal,
|
||||||
|
overwrite: Overwrite,
|
||||||
|
) -> Result<Response, R::Error> {
|
||||||
|
// 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())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user