mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 20:32:48 +00:00
Work on axum support
This commit is contained in:
91
Cargo.lock
generated
91
Cargo.lock
generated
@@ -489,6 +489,83 @@ version = "1.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
|
||||||
|
dependencies = [
|
||||||
|
"axum-core",
|
||||||
|
"bytes",
|
||||||
|
"form_urlencoded",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.3.1",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
|
"itoa",
|
||||||
|
"matchit",
|
||||||
|
"memchr",
|
||||||
|
"mime",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustversion",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_path_to_error",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"sync_wrapper",
|
||||||
|
"tokio",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-core"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"http 1.3.1",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"mime",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustversion",
|
||||||
|
"sync_wrapper",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-extra"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"axum-core",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"headers",
|
||||||
|
"http 1.3.1",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"mime",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustversion",
|
||||||
|
"serde",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.75"
|
version = "0.3.75"
|
||||||
@@ -1565,6 +1642,7 @@ dependencies = [
|
|||||||
"http 1.3.1",
|
"http 1.3.1",
|
||||||
"http-body",
|
"http-body",
|
||||||
"httparse",
|
"httparse",
|
||||||
|
"httpdate",
|
||||||
"itoa",
|
"itoa",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
@@ -1974,6 +2052,12 @@ dependencies = [
|
|||||||
"regex-automata 0.1.10",
|
"regex-automata 0.1.10",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matchit"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "md-5"
|
name = "md-5"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
@@ -3076,6 +3160,7 @@ dependencies = [
|
|||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
"derive_more 2.0.1",
|
"derive_more 2.0.1",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"http 1.3.1",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"rustical_dav",
|
"rustical_dav",
|
||||||
"rustical_dav_push",
|
"rustical_dav_push",
|
||||||
@@ -3103,6 +3188,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"derive_more 2.0.1",
|
"derive_more 2.0.1",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"http 1.3.1",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"rustical_dav",
|
"rustical_dav",
|
||||||
"rustical_dav_push",
|
"rustical_dav_push",
|
||||||
@@ -3124,10 +3210,13 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"axum",
|
||||||
|
"axum-extra",
|
||||||
"derive_more 2.0.1",
|
"derive_more 2.0.1",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"headers",
|
"headers",
|
||||||
"http 0.2.12",
|
"http 0.2.12",
|
||||||
|
"http 1.3.1",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"log",
|
"log",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
@@ -3135,6 +3224,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tower",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-actix-web",
|
"tracing-actix-web",
|
||||||
]
|
]
|
||||||
@@ -3147,6 +3237,7 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"derive_more 2.0.1",
|
"derive_more 2.0.1",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"http 1.3.1",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"log",
|
"log",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ sqlx = { version = "0.8", default-features = false, features = [
|
|||||||
"migrate",
|
"migrate",
|
||||||
"json",
|
"json",
|
||||||
] }
|
] }
|
||||||
http = "0.2" # This version is used by actix-web
|
http_02 = { package = "http", version = "0.2" } # actix-web uses a very outdated version
|
||||||
|
http = "1.3"
|
||||||
headers = "0.4"
|
headers = "0.4"
|
||||||
strum = "0.27"
|
strum = "0.27"
|
||||||
strum_macros = "0.27"
|
strum_macros = "0.27"
|
||||||
|
|||||||
@@ -29,3 +29,4 @@ rustical_xml.workspace = true
|
|||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
rustical_dav_push.workspace = true
|
rustical_dav_push.workspace = true
|
||||||
rustical_ical.workspace = true
|
rustical_ical.workspace = true
|
||||||
|
http.workspace = true
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
HttpRequest, Responder,
|
HttpRequest, Responder,
|
||||||
http::StatusCode,
|
|
||||||
web::{Data, Path},
|
web::{Data, Path},
|
||||||
};
|
};
|
||||||
use calendar_multiget::{CalendarMultigetRequest, get_objects_calendar_multiget};
|
use calendar_multiget::{CalendarMultigetRequest, get_objects_calendar_multiget};
|
||||||
use calendar_query::{CalendarQueryRequest, get_objects_calendar_query};
|
use calendar_query::{CalendarQueryRequest, get_objects_calendar_query};
|
||||||
|
use http::StatusCode;
|
||||||
use rustical_dav::{
|
use rustical_dav::{
|
||||||
resource::{PrincipalUri, Resource},
|
resource::{PrincipalUri, Resource},
|
||||||
xml::{
|
xml::{
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::{
|
|||||||
CalendarObjectPropWrapper, CalendarObjectPropWrapperName, CalendarObjectResource,
|
CalendarObjectPropWrapper, CalendarObjectPropWrapperName, CalendarObjectResource,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use actix_web::http::StatusCode;
|
use http::StatusCode;
|
||||||
use rustical_dav::{
|
use rustical_dav::{
|
||||||
resource::{PrincipalUri, Resource},
|
resource::{PrincipalUri, Resource},
|
||||||
xml::{
|
xml::{
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ impl<C: CalendarStore, S: SubscriptionStore> CalendarResourceService<C, S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait]
|
||||||
impl<C: CalendarStore, S: SubscriptionStore> ResourceService for CalendarResourceService<C, S> {
|
impl<C: CalendarStore, S: SubscriptionStore> ResourceService for CalendarResourceService<C, S> {
|
||||||
type MemberType = CalendarObjectResource;
|
type MemberType = CalendarObjectResource;
|
||||||
type PathComponents = (String, String); // principal, calendar_id
|
type PathComponents = (String, String); // principal, calendar_id
|
||||||
@@ -331,6 +331,8 @@ impl<C: CalendarStore, S: SubscriptionStore> ResourceService for CalendarResourc
|
|||||||
type Principal = User;
|
type Principal = User;
|
||||||
type PrincipalUri = CalDavPrincipalUri;
|
type PrincipalUri = CalDavPrincipalUri;
|
||||||
|
|
||||||
|
const DAV_HEADER: &str = "1, 3, access-control, calendar-access";
|
||||||
|
|
||||||
async fn get_resource(
|
async fn get_resource(
|
||||||
&self,
|
&self,
|
||||||
(principal, cal_id): &Self::PathComponents,
|
(principal, cal_id): &Self::PathComponents,
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ pub struct CalendarObjectPathComponents {
|
|||||||
pub object_id: String,
|
pub object_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait]
|
||||||
impl<C: CalendarStore> ResourceService for CalendarObjectResourceService<C> {
|
impl<C: CalendarStore> ResourceService for CalendarObjectResourceService<C> {
|
||||||
type PathComponents = CalendarObjectPathComponents;
|
type PathComponents = CalendarObjectPathComponents;
|
||||||
type Resource = CalendarObjectResource;
|
type Resource = CalendarObjectResource;
|
||||||
@@ -146,6 +146,8 @@ impl<C: CalendarStore> ResourceService for CalendarObjectResourceService<C> {
|
|||||||
type Principal = User;
|
type Principal = User;
|
||||||
type PrincipalUri = CalDavPrincipalUri;
|
type PrincipalUri = CalDavPrincipalUri;
|
||||||
|
|
||||||
|
const DAV_HEADER: &str = "1, 3, access-control, calendar-access";
|
||||||
|
|
||||||
async fn get_resource(
|
async fn get_resource(
|
||||||
&self,
|
&self,
|
||||||
CalendarObjectPathComponents {
|
CalendarObjectPathComponents {
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ impl<C: CalendarStore, S: SubscriptionStore> CalendarSetResourceService<C, S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait]
|
||||||
impl<C: CalendarStore, S: SubscriptionStore> ResourceService for CalendarSetResourceService<C, S> {
|
impl<C: CalendarStore, S: SubscriptionStore> ResourceService for CalendarSetResourceService<C, S> {
|
||||||
type PathComponents = (String,);
|
type PathComponents = (String,);
|
||||||
type MemberType = CalendarResource;
|
type MemberType = CalendarResource;
|
||||||
@@ -86,6 +86,8 @@ impl<C: CalendarStore, S: SubscriptionStore> ResourceService for CalendarSetReso
|
|||||||
type Principal = User;
|
type Principal = User;
|
||||||
type PrincipalUri = CalDavPrincipalUri;
|
type PrincipalUri = CalDavPrincipalUri;
|
||||||
|
|
||||||
|
const DAV_HEADER: &str = "1, 3, access-control, extended-mkcol";
|
||||||
|
|
||||||
async fn get_resource(
|
async fn get_resource(
|
||||||
&self,
|
&self,
|
||||||
(principal,): &Self::PathComponents,
|
(principal,): &Self::PathComponents,
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ impl actix_web::ResponseError for Error {
|
|||||||
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
},
|
},
|
||||||
Error::ChronoParseError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
Error::ChronoParseError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Error::DavError(err) => err.status_code(),
|
Error::DavError(err) => StatusCode::try_from(err.status_code().as_u16())
|
||||||
|
.expect("Just converting between versions"),
|
||||||
Error::Unauthorized => StatusCode::UNAUTHORIZED,
|
Error::Unauthorized => StatusCode::UNAUTHORIZED,
|
||||||
Error::XmlDecodeError(_) => StatusCode::BAD_REQUEST,
|
Error::XmlDecodeError(_) => StatusCode::BAD_REQUEST,
|
||||||
Error::NotImplemented => StatusCode::INTERNAL_SERVER_ERROR,
|
Error::NotImplemented => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore, BS: Ca
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait]
|
||||||
impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore, BS: CalendarStore>
|
impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore, BS: CalendarStore>
|
||||||
ResourceService for PrincipalResourceService<AP, S, CS, BS>
|
ResourceService for PrincipalResourceService<AP, S, CS, BS>
|
||||||
{
|
{
|
||||||
@@ -156,6 +156,8 @@ impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore, BS: Ca
|
|||||||
type Principal = User;
|
type Principal = User;
|
||||||
type PrincipalUri = CalDavPrincipalUri;
|
type PrincipalUri = CalDavPrincipalUri;
|
||||||
|
|
||||||
|
const DAV_HEADER: &str = "1, 3, access-control";
|
||||||
|
|
||||||
async fn get_resource(
|
async fn get_resource(
|
||||||
&self,
|
&self,
|
||||||
(principal,): &Self::PathComponents,
|
(principal,): &Self::PathComponents,
|
||||||
|
|||||||
@@ -27,3 +27,4 @@ rustical_xml.workspace = true
|
|||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
rustical_dav_push.workspace = true
|
rustical_dav_push.workspace = true
|
||||||
rustical_ical.workspace = true
|
rustical_ical.workspace = true
|
||||||
|
http.workspace = true
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ pub struct AddressObjectPathComponents {
|
|||||||
pub object_id: String,
|
pub object_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait]
|
||||||
impl<AS: AddressbookStore> ResourceService for AddressObjectResourceService<AS> {
|
impl<AS: AddressbookStore> ResourceService for AddressObjectResourceService<AS> {
|
||||||
type PathComponents = AddressObjectPathComponents;
|
type PathComponents = AddressObjectPathComponents;
|
||||||
type Resource = AddressObjectResource;
|
type Resource = AddressObjectResource;
|
||||||
@@ -114,6 +114,8 @@ impl<AS: AddressbookStore> ResourceService for AddressObjectResourceService<AS>
|
|||||||
type Principal = User;
|
type Principal = User;
|
||||||
type PrincipalUri = CardDavPrincipalUri;
|
type PrincipalUri = CardDavPrincipalUri;
|
||||||
|
|
||||||
|
const DAV_HEADER: &str = "1, 3, access-control, addressbook";
|
||||||
|
|
||||||
async fn get_resource(
|
async fn get_resource(
|
||||||
&self,
|
&self,
|
||||||
AddressObjectPathComponents {
|
AddressObjectPathComponents {
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ use crate::{
|
|||||||
AddressObjectPropWrapper, AddressObjectPropWrapperName, AddressObjectResource,
|
AddressObjectPropWrapper, AddressObjectPropWrapperName, AddressObjectResource,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use actix_web::{
|
use actix_web::dev::{Path, ResourceDef};
|
||||||
dev::{Path, ResourceDef},
|
|
||||||
http::StatusCode,
|
use http::StatusCode;
|
||||||
};
|
|
||||||
use rustical_dav::{
|
use rustical_dav::{
|
||||||
resource::{PrincipalUri, Resource},
|
resource::{PrincipalUri, Resource},
|
||||||
xml::{MultistatusElement, PropfindType, multistatus::ResponseElement},
|
xml::{MultistatusElement, PropfindType, multistatus::ResponseElement},
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::{
|
|||||||
AddressObjectPropWrapper, AddressObjectPropWrapperName, AddressObjectResource,
|
AddressObjectPropWrapper, AddressObjectPropWrapperName, AddressObjectResource,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use actix_web::http::StatusCode;
|
use http::StatusCode;
|
||||||
use rustical_dav::{
|
use rustical_dav::{
|
||||||
resource::{PrincipalUri, Resource},
|
resource::{PrincipalUri, Resource},
|
||||||
xml::{
|
xml::{
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ impl Resource for AddressbookResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait]
|
||||||
impl<AS: AddressbookStore, S: SubscriptionStore> ResourceService
|
impl<AS: AddressbookStore, S: SubscriptionStore> ResourceService
|
||||||
for AddressbookResourceService<AS, S>
|
for AddressbookResourceService<AS, S>
|
||||||
{
|
{
|
||||||
@@ -199,6 +199,8 @@ impl<AS: AddressbookStore, S: SubscriptionStore> ResourceService
|
|||||||
type Principal = User;
|
type Principal = User;
|
||||||
type PrincipalUri = CardDavPrincipalUri;
|
type PrincipalUri = CardDavPrincipalUri;
|
||||||
|
|
||||||
|
const DAV_HEADER: &str = "1, 3, access-control, addressbook";
|
||||||
|
|
||||||
async fn get_resource(
|
async fn get_resource(
|
||||||
&self,
|
&self,
|
||||||
(principal, addressbook_id): &Self::PathComponents,
|
(principal, addressbook_id): &Self::PathComponents,
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ impl actix_web::ResponseError for Error {
|
|||||||
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
},
|
},
|
||||||
Error::ChronoParseError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
Error::ChronoParseError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Error::DavError(err) => err.status_code(),
|
Error::DavError(err) => StatusCode::try_from(err.status_code().as_u16())
|
||||||
|
.expect("Just converting between versions"),
|
||||||
Error::Unauthorized => StatusCode::UNAUTHORIZED,
|
Error::Unauthorized => StatusCode::UNAUTHORIZED,
|
||||||
Error::XmlDecodeError(_) => StatusCode::BAD_REQUEST,
|
Error::XmlDecodeError(_) => StatusCode::BAD_REQUEST,
|
||||||
Error::NotImplemented => StatusCode::INTERNAL_SERVER_ERROR,
|
Error::NotImplemented => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ impl Resource for PrincipalResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait]
|
||||||
impl<A: AddressbookStore, AP: AuthenticationProvider, S: SubscriptionStore> ResourceService
|
impl<A: AddressbookStore, AP: AuthenticationProvider, S: SubscriptionStore> ResourceService
|
||||||
for PrincipalResourceService<A, AP, S>
|
for PrincipalResourceService<A, AP, S>
|
||||||
{
|
{
|
||||||
@@ -151,6 +151,8 @@ impl<A: AddressbookStore, AP: AuthenticationProvider, S: SubscriptionStore> Reso
|
|||||||
type Principal = User;
|
type Principal = User;
|
||||||
type PrincipalUri = CardDavPrincipalUri;
|
type PrincipalUri = CardDavPrincipalUri;
|
||||||
|
|
||||||
|
const DAV_HEADER: &str = "1, 3, access-control, addressbook";
|
||||||
|
|
||||||
async fn get_resource(
|
async fn get_resource(
|
||||||
&self,
|
&self,
|
||||||
(principal,): &Self::PathComponents,
|
(principal,): &Self::PathComponents,
|
||||||
|
|||||||
@@ -7,9 +7,16 @@ repository.workspace = true
|
|||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
actix = ["dep:actix-web", "dep:tracing-actix-web"]
|
actix = ["dep:actix-web", "dep:tracing-actix-web", "dep:http_02"]
|
||||||
|
axum = ["dep:axum", "dep:axum-extra", "dep:tower"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
axum = { version = "0.8", optional = true }
|
||||||
|
axum-extra = { version = "0.10", optional = true, features = ["typed-header"] }
|
||||||
|
tower = { version = "0.5", optional = true }
|
||||||
|
|
||||||
|
http_02 = { workspace = true, optional = true }
|
||||||
|
|
||||||
rustical_xml.workspace = true
|
rustical_xml.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
futures-util.workspace = true
|
futures-util.workspace = true
|
||||||
|
|||||||
@@ -55,17 +55,40 @@ impl Error {
|
|||||||
|
|
||||||
#[cfg(feature = "actix")]
|
#[cfg(feature = "actix")]
|
||||||
impl actix_web::error::ResponseError for Error {
|
impl actix_web::error::ResponseError for Error {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> actix_web::http::StatusCode {
|
||||||
self.status_code()
|
self.status_code()
|
||||||
|
.as_u16()
|
||||||
|
.try_into()
|
||||||
|
.expect("Just converting between versions")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn error_response(&self) -> actix_web::HttpResponse {
|
fn error_response(&self) -> actix_web::HttpResponse {
|
||||||
|
use actix_web::ResponseError;
|
||||||
|
|
||||||
error!("Error: {self}");
|
error!("Error: {self}");
|
||||||
match self {
|
match self {
|
||||||
Error::Unauthorized => actix_web::HttpResponse::build(self.status_code())
|
Error::Unauthorized => actix_web::HttpResponse::build(ResponseError::status_code(self))
|
||||||
.append_header(("WWW-Authenticate", "Basic"))
|
.append_header(("WWW-Authenticate", "Basic"))
|
||||||
.body(self.to_string()),
|
.body(self.to_string()),
|
||||||
_ => actix_web::HttpResponse::build(self.status_code()).body(self.to_string()),
|
_ => actix_web::HttpResponse::build(ResponseError::status_code(self))
|
||||||
|
.body(self.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
impl axum::response::IntoResponse for Error {
|
||||||
|
fn into_response(self) -> axum::response::Response {
|
||||||
|
use axum::body::Body;
|
||||||
|
|
||||||
|
let mut resp = axum::response::Response::builder().status(self.status_code());
|
||||||
|
if matches!(&self, &Error::Unauthorized) {
|
||||||
|
resp.headers_mut()
|
||||||
|
.expect("This must always work")
|
||||||
|
.insert("WWW-Authenticate", "Basic".parse().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.body(Body::new(self.to_string()))
|
||||||
|
.expect("This should always work")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#[cfg(feature = "actix")]
|
#[cfg(feature = "actix")]
|
||||||
use actix_web::{HttpRequest, ResponseError};
|
use actix_web::{HttpRequest, ResponseError};
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
use axum::{body::Body, extract::FromRequestParts, response::IntoResponse};
|
||||||
use futures_util::future::{Ready, err, ok};
|
use futures_util::future::{Ready, err, ok};
|
||||||
use http::StatusCode;
|
|
||||||
use rustical_xml::{ValueDeserialize, ValueSerialize, XmlError};
|
use rustical_xml::{ValueDeserialize, ValueSerialize, XmlError};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
@@ -12,7 +13,17 @@ pub struct InvalidDepthHeader;
|
|||||||
#[cfg(feature = "actix")]
|
#[cfg(feature = "actix")]
|
||||||
impl ResponseError for InvalidDepthHeader {
|
impl ResponseError for InvalidDepthHeader {
|
||||||
fn status_code(&self) -> actix_web::http::StatusCode {
|
fn status_code(&self) -> actix_web::http::StatusCode {
|
||||||
StatusCode::BAD_REQUEST
|
http_02::StatusCode::BAD_REQUEST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
impl IntoResponse for InvalidDepthHeader {
|
||||||
|
fn into_response(self) -> axum::response::Response {
|
||||||
|
axum::response::Response::builder()
|
||||||
|
.status(axum::http::StatusCode::BAD_REQUEST)
|
||||||
|
.body(Body::empty())
|
||||||
|
.expect("this always works")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,3 +92,19 @@ impl actix_web::FromRequest for Depth {
|
|||||||
Self::extract(req)
|
Self::extract(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
impl<S: Send + Sync> FromRequestParts<S> for Depth {
|
||||||
|
type Rejection = InvalidDepthHeader;
|
||||||
|
|
||||||
|
async fn from_request_parts(
|
||||||
|
parts: &mut axum::http::request::Parts,
|
||||||
|
state: &S,
|
||||||
|
) -> Result<Self, Self::Rejection> {
|
||||||
|
if let Some(depth_header) = parts.headers.get("Depth") {
|
||||||
|
depth_header.as_bytes().try_into()
|
||||||
|
} else {
|
||||||
|
Ok(Self::Zero)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,6 @@ pub mod xml;
|
|||||||
|
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
|
|
||||||
pub trait Principal: std::fmt::Debug + Clone + 'static {
|
pub trait Principal: std::fmt::Debug + Clone + Send + Sync + 'static {
|
||||||
fn get_id(&self) -> &str;
|
fn get_id(&self) -> &str;
|
||||||
}
|
}
|
||||||
|
|||||||
94
crates/dav/src/resource/axum_methods.rs
Normal file
94
crates/dav/src/resource/axum_methods.rs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
use axum::body::Body;
|
||||||
|
use futures_util::future::BoxFuture;
|
||||||
|
use headers::Allow;
|
||||||
|
use http::{Method, Request, Response};
|
||||||
|
use std::{convert::Infallible, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
|
pub type MethodFunction<Service> =
|
||||||
|
fn(Arc<Service>, Request<Body>) -> BoxFuture<'static, Result<Response<Body>, Infallible>>;
|
||||||
|
|
||||||
|
pub trait AxumMethods: Sized + Send + Sync + 'static {
|
||||||
|
#[inline]
|
||||||
|
fn report() -> Option<MethodFunction<Self>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get() -> Option<MethodFunction<Self>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn head() -> Option<MethodFunction<Self>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn post() -> Option<MethodFunction<Self>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn mkcol() -> Option<MethodFunction<Self>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn mkcalendar() -> Option<MethodFunction<Self>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn copy() -> Option<MethodFunction<Self>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn mv() -> Option<MethodFunction<Self>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn put() -> Option<MethodFunction<Self>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn allow_header() -> Allow {
|
||||||
|
let mut allow = vec![
|
||||||
|
Method::from_str("PROPFIND").unwrap(),
|
||||||
|
Method::from_str("PROPPATCH").unwrap(),
|
||||||
|
Method::DELETE,
|
||||||
|
Method::OPTIONS,
|
||||||
|
];
|
||||||
|
if Self::report().is_some() {
|
||||||
|
allow.push(Method::from_str("REPORT").unwrap());
|
||||||
|
}
|
||||||
|
if Self::get().is_some() {
|
||||||
|
allow.push(Method::GET);
|
||||||
|
}
|
||||||
|
if Self::head().is_some() {
|
||||||
|
allow.push(Method::HEAD);
|
||||||
|
}
|
||||||
|
if Self::post().is_some() {
|
||||||
|
allow.push(Method::POST);
|
||||||
|
}
|
||||||
|
if Self::mkcol().is_some() {
|
||||||
|
allow.push(Method::from_str("MKCOL").unwrap());
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
allow.into_iter().collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
120
crates/dav/src/resource/axum_service.rs
Normal file
120
crates/dav/src/resource/axum_service.rs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
use super::methods::{axum_route_propfind, axum_route_proppatch};
|
||||||
|
use crate::resource::{ResourceService, axum_methods::AxumMethods};
|
||||||
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
handler::Handler,
|
||||||
|
http::{Request, Response},
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
|
use futures_util::future::BoxFuture;
|
||||||
|
use headers::HeaderMapExt;
|
||||||
|
use http::{HeaderValue, StatusCode};
|
||||||
|
use std::{convert::Infallible, sync::Arc};
|
||||||
|
use tower::Service;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AxumService<RS: ResourceService + AxumMethods> {
|
||||||
|
resource_service: Arc<RS>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<RS: ResourceService + AxumMethods> AxumService<RS> {
|
||||||
|
pub fn new(resource_service: Arc<RS>) -> Self {
|
||||||
|
Self { resource_service }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<RS: ResourceService + AxumMethods + Clone + Send + Sync> Service<Request<Body>>
|
||||||
|
for AxumService<RS>
|
||||||
|
where
|
||||||
|
RS::Error: IntoResponse + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
type Error = Infallible;
|
||||||
|
type Response = Response<Body>;
|
||||||
|
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn poll_ready(
|
||||||
|
&mut self,
|
||||||
|
_cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||||
|
Ok(()).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn call(&mut self, req: Request<Body>) -> Self::Future {
|
||||||
|
use crate::resource::methods::axum_route_delete;
|
||||||
|
let mut propfind_service =
|
||||||
|
Handler::with_state(axum_route_propfind::<RS>, self.resource_service.clone());
|
||||||
|
let mut proppatch_service =
|
||||||
|
Handler::with_state(axum_route_proppatch::<RS>, self.resource_service.clone());
|
||||||
|
let mut delete_service =
|
||||||
|
Handler::with_state(axum_route_delete::<RS>, self.resource_service.clone());
|
||||||
|
let mut options_service = Handler::with_state(route_options::<RS>, ());
|
||||||
|
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)),
|
||||||
|
"REPORT" => {
|
||||||
|
if let Some(svc) = RS::report() {
|
||||||
|
return svc(self.resource_service.clone(), req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"GET" => {
|
||||||
|
if let Some(svc) = RS::get() {
|
||||||
|
return svc(self.resource_service.clone(), req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"HEAD" => {
|
||||||
|
if let Some(svc) = RS::head() {
|
||||||
|
return svc(self.resource_service.clone(), req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"POST" => {
|
||||||
|
if let Some(svc) = RS::post() {
|
||||||
|
return svc(self.resource_service.clone(), req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"MKCOL" => {
|
||||||
|
if let Some(svc) = RS::mkcol() {
|
||||||
|
return svc(self.resource_service.clone(), req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"MKCALENDAR" => {
|
||||||
|
if let Some(svc) = RS::mkcalendar() {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
Box::pin(async move {
|
||||||
|
Ok(Response::builder()
|
||||||
|
.status(StatusCode::METHOD_NOT_ALLOWED)
|
||||||
|
.body(Body::from("Method not allowed"))
|
||||||
|
.unwrap())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn route_options<RS: ResourceService + AxumMethods>() -> Response<Body> {
|
||||||
|
let mut resp = Response::builder().status(StatusCode::OK);
|
||||||
|
let headers = resp.headers_mut().unwrap();
|
||||||
|
headers.insert("DAV", HeaderValue::from_static(RS::DAV_HEADER));
|
||||||
|
headers.typed_insert(RS::allow_header());
|
||||||
|
resp.body(Body::empty()).unwrap()
|
||||||
|
}
|
||||||
@@ -2,9 +2,17 @@ use crate::Error;
|
|||||||
use crate::privileges::UserPrivilege;
|
use crate::privileges::UserPrivilege;
|
||||||
use crate::resource::Resource;
|
use crate::resource::Resource;
|
||||||
use crate::resource::ResourceService;
|
use crate::resource::ResourceService;
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
use axum::extract::{Extension, Path, State};
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
use axum_extra::TypedHeader;
|
||||||
use headers::Header;
|
use headers::Header;
|
||||||
use headers::{HeaderValue, IfMatch, IfNoneMatch};
|
use headers::{HeaderValue, IfMatch, IfNoneMatch};
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
use http::HeaderMap;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
use std::sync::Arc;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
#[cfg(feature = "actix")]
|
#[cfg(feature = "actix")]
|
||||||
@@ -27,12 +35,12 @@ pub async fn actix_route_delete<R: ResourceService>(
|
|||||||
// while actix-web still uses http==0.2
|
// while actix-web still uses http==0.2
|
||||||
let if_match = req
|
let if_match = req
|
||||||
.headers()
|
.headers()
|
||||||
.get_all(http::header::IF_MATCH)
|
.get_all(http_02::header::IF_MATCH)
|
||||||
.map(|val_02| HeaderValue::from_bytes(val_02.as_bytes()).unwrap())
|
.map(|val_02| HeaderValue::from_bytes(val_02.as_bytes()).unwrap())
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
let if_none_match = req
|
let if_none_match = req
|
||||||
.headers()
|
.headers()
|
||||||
.get_all(http::header::IF_NONE_MATCH)
|
.get_all(http_02::header::IF_NONE_MATCH)
|
||||||
.map(|val_02| HeaderValue::from_bytes(val_02.as_bytes()).unwrap())
|
.map(|val_02| HeaderValue::from_bytes(val_02.as_bytes()).unwrap())
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
|
|
||||||
@@ -49,7 +57,7 @@ pub async fn actix_route_delete<R: ResourceService>(
|
|||||||
|
|
||||||
route_delete(
|
route_delete(
|
||||||
&path.into_inner(),
|
&path.into_inner(),
|
||||||
principal,
|
&principal,
|
||||||
resource_service.as_ref(),
|
resource_service.as_ref(),
|
||||||
no_trash,
|
no_trash,
|
||||||
if_match,
|
if_match,
|
||||||
@@ -60,9 +68,33 @@ pub async fn actix_route_delete<R: ResourceService>(
|
|||||||
Ok(actix_web::HttpResponse::Ok().body(""))
|
Ok(actix_web::HttpResponse::Ok().body(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
pub(crate) async fn axum_route_delete<R: ResourceService>(
|
||||||
|
Path(path): Path<R::PathComponents>,
|
||||||
|
State(resource_service): State<Arc<R>>,
|
||||||
|
Extension(principal): Extension<R::Principal>,
|
||||||
|
if_match: Option<TypedHeader<IfMatch>>,
|
||||||
|
if_none_match: Option<TypedHeader<IfNoneMatch>>,
|
||||||
|
header_map: HeaderMap,
|
||||||
|
) -> Result<(), R::Error> {
|
||||||
|
let no_trash = header_map
|
||||||
|
.get("X-No-Trashbin")
|
||||||
|
.map(|val| matches!(val.to_str(), Ok("1")))
|
||||||
|
.unwrap_or(false);
|
||||||
|
route_delete(
|
||||||
|
&path,
|
||||||
|
&principal,
|
||||||
|
resource_service.as_ref(),
|
||||||
|
no_trash,
|
||||||
|
if_match.map(|hdr| hdr.0),
|
||||||
|
if_none_match.map(|hdr| hdr.0),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn route_delete<R: ResourceService>(
|
pub async fn route_delete<R: ResourceService>(
|
||||||
path_components: &R::PathComponents,
|
path_components: &R::PathComponents,
|
||||||
principal: R::Principal,
|
principal: &R::Principal,
|
||||||
resource_service: &R,
|
resource_service: &R,
|
||||||
no_trash: bool,
|
no_trash: bool,
|
||||||
if_match: Option<IfMatch>,
|
if_match: Option<IfMatch>,
|
||||||
@@ -70,7 +102,7 @@ pub async fn route_delete<R: ResourceService>(
|
|||||||
) -> Result<(), R::Error> {
|
) -> Result<(), R::Error> {
|
||||||
let resource = resource_service.get_resource(path_components).await?;
|
let resource = resource_service.get_resource(path_components).await?;
|
||||||
|
|
||||||
let privileges = resource.get_user_privileges(&principal)?;
|
let privileges = resource.get_user_privileges(principal)?;
|
||||||
if !privileges.has(&UserPrivilege::Write) {
|
if !privileges.has(&UserPrivilege::Write) {
|
||||||
return Err(Error::Unauthorized.into());
|
return Err(Error::Unauthorized.into());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,17 @@ mod delete;
|
|||||||
mod propfind;
|
mod propfind;
|
||||||
mod proppatch;
|
mod proppatch;
|
||||||
|
|
||||||
pub(crate) use delete::route_delete;
|
|
||||||
pub(crate) use propfind::route_propfind;
|
|
||||||
pub(crate) use proppatch::route_proppatch;
|
|
||||||
|
|
||||||
#[cfg(feature = "actix")]
|
#[cfg(feature = "actix")]
|
||||||
pub(crate) use delete::actix_route_delete;
|
pub(crate) use delete::actix_route_delete;
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
pub(crate) use delete::axum_route_delete;
|
||||||
|
|
||||||
#[cfg(feature = "actix")]
|
#[cfg(feature = "actix")]
|
||||||
pub(crate) use propfind::actix_route_propfind;
|
pub(crate) use propfind::actix_route_propfind;
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
pub(crate) use propfind::axum_route_propfind;
|
||||||
|
|
||||||
#[cfg(feature = "actix")]
|
#[cfg(feature = "actix")]
|
||||||
pub(crate) use proppatch::actix_route_proppatch;
|
pub(crate) use proppatch::actix_route_proppatch;
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
pub(crate) use proppatch::axum_route_proppatch;
|
||||||
|
|||||||
@@ -7,8 +7,11 @@ use crate::resource::ResourceService;
|
|||||||
use crate::xml::MultistatusElement;
|
use crate::xml::MultistatusElement;
|
||||||
use crate::xml::PropfindElement;
|
use crate::xml::PropfindElement;
|
||||||
use crate::xml::PropfindType;
|
use crate::xml::PropfindType;
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
use axum::extract::{Extension, OriginalUri, Path, State};
|
||||||
use rustical_xml::PropName;
|
use rustical_xml::PropName;
|
||||||
use rustical_xml::XmlDocument;
|
use rustical_xml::XmlDocument;
|
||||||
|
use std::sync::Arc;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
#[cfg(feature = "actix")]
|
#[cfg(feature = "actix")]
|
||||||
@@ -30,21 +33,46 @@ pub(crate) async fn actix_route_propfind<R: ResourceService>(
|
|||||||
route_propfind(
|
route_propfind(
|
||||||
&path.into_inner(),
|
&path.into_inner(),
|
||||||
req.path(),
|
req.path(),
|
||||||
body,
|
&body,
|
||||||
user,
|
&user,
|
||||||
depth,
|
&depth,
|
||||||
resource_service.as_ref(),
|
resource_service.as_ref(),
|
||||||
puri.as_ref(),
|
puri.as_ref(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
pub(crate) async fn axum_route_propfind<R: ResourceService>(
|
||||||
|
Path(path): Path<R::PathComponents>,
|
||||||
|
State(resource_service): State<Arc<R>>,
|
||||||
|
depth: Depth,
|
||||||
|
Extension(principal): Extension<R::Principal>,
|
||||||
|
uri: OriginalUri,
|
||||||
|
Extension(puri): Extension<R::PrincipalUri>,
|
||||||
|
body: String,
|
||||||
|
) -> Result<
|
||||||
|
MultistatusElement<<R::Resource as Resource>::Prop, <R::MemberType as Resource>::Prop>,
|
||||||
|
R::Error,
|
||||||
|
> {
|
||||||
|
route_propfind::<R>(
|
||||||
|
&path,
|
||||||
|
uri.path(),
|
||||||
|
&body,
|
||||||
|
&principal,
|
||||||
|
&depth,
|
||||||
|
resource_service.as_ref(),
|
||||||
|
&puri,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn route_propfind<R: ResourceService>(
|
pub(crate) async fn route_propfind<R: ResourceService>(
|
||||||
path_components: &R::PathComponents,
|
path_components: &R::PathComponents,
|
||||||
path: &str,
|
path: &str,
|
||||||
body: String,
|
body: &str,
|
||||||
user: R::Principal,
|
principal: &R::Principal,
|
||||||
depth: Depth,
|
depth: &Depth,
|
||||||
resource_service: &R,
|
resource_service: &R,
|
||||||
puri: &impl PrincipalUri,
|
puri: &impl PrincipalUri,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
@@ -52,7 +80,7 @@ pub(crate) async fn route_propfind<R: ResourceService>(
|
|||||||
R::Error,
|
R::Error,
|
||||||
> {
|
> {
|
||||||
let resource = resource_service.get_resource(path_components).await?;
|
let resource = resource_service.get_resource(path_components).await?;
|
||||||
let privileges = resource.get_user_privileges(&user)?;
|
let privileges = resource.get_user_privileges(principal)?;
|
||||||
if !privileges.has(&UserPrivilege::Read) {
|
if !privileges.has(&UserPrivilege::Read) {
|
||||||
return Err(Error::Unauthorized.into());
|
return Err(Error::Unauthorized.into());
|
||||||
}
|
}
|
||||||
@@ -60,7 +88,7 @@ pub(crate) async fn route_propfind<R: ResourceService>(
|
|||||||
// A request body is optional. If empty we MUST return all props
|
// A request body is optional. If empty we MUST return all props
|
||||||
let propfind_self: PropfindElement<<<R::Resource as Resource>::Prop as PropName>::Names> =
|
let propfind_self: PropfindElement<<<R::Resource as Resource>::Prop as PropName>::Names> =
|
||||||
if !body.is_empty() {
|
if !body.is_empty() {
|
||||||
PropfindElement::parse_str(&body).map_err(Error::XmlError)?
|
PropfindElement::parse_str(body).map_err(Error::XmlError)?
|
||||||
} else {
|
} else {
|
||||||
PropfindElement {
|
PropfindElement {
|
||||||
prop: PropfindType::Allprop,
|
prop: PropfindType::Allprop,
|
||||||
@@ -68,7 +96,7 @@ pub(crate) async fn route_propfind<R: ResourceService>(
|
|||||||
};
|
};
|
||||||
let propfind_member: PropfindElement<<<R::MemberType as Resource>::Prop as PropName>::Names> =
|
let propfind_member: PropfindElement<<<R::MemberType as Resource>::Prop as PropName>::Names> =
|
||||||
if !body.is_empty() {
|
if !body.is_empty() {
|
||||||
PropfindElement::parse_str(&body).map_err(Error::XmlError)?
|
PropfindElement::parse_str(body).map_err(Error::XmlError)?
|
||||||
} else {
|
} else {
|
||||||
PropfindElement {
|
PropfindElement {
|
||||||
prop: PropfindType::Allprop,
|
prop: PropfindType::Allprop,
|
||||||
@@ -76,18 +104,18 @@ pub(crate) async fn route_propfind<R: ResourceService>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut member_responses = Vec::new();
|
let mut member_responses = Vec::new();
|
||||||
if depth != Depth::Zero {
|
if depth != &Depth::Zero {
|
||||||
for (subpath, member) in resource_service.get_members(path_components).await? {
|
for (subpath, member) in resource_service.get_members(path_components).await? {
|
||||||
member_responses.push(member.propfind_typed(
|
member_responses.push(member.propfind_typed(
|
||||||
&format!("{}/{}", path.trim_end_matches('/'), subpath),
|
&format!("{}/{}", path.trim_end_matches('/'), subpath),
|
||||||
&propfind_member.prop,
|
&propfind_member.prop,
|
||||||
puri,
|
puri,
|
||||||
&user,
|
principal,
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = resource.propfind_typed(path, &propfind_self.prop, puri, &user)?;
|
let response = resource.propfind_typed(path, &propfind_self.prop, puri, &principal)?;
|
||||||
|
|
||||||
Ok(MultistatusElement {
|
Ok(MultistatusElement {
|
||||||
responses: vec![response],
|
responses: vec![response],
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
use crate::Error;
|
use crate::Error;
|
||||||
use crate::privileges::UserPrivilege;
|
use crate::privileges::UserPrivilege;
|
||||||
|
use std::sync::Arc;
|
||||||
use crate::resource::Resource;
|
use crate::resource::Resource;
|
||||||
use crate::resource::ResourceService;
|
use crate::resource::ResourceService;
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
use axum::extract::{Extension, OriginalUri, Path, State};
|
||||||
use crate::xml::MultistatusElement;
|
use crate::xml::MultistatusElement;
|
||||||
use crate::xml::TagList;
|
use crate::xml::TagList;
|
||||||
use crate::xml::multistatus::{PropstatElement, PropstatWrapper, ResponseElement};
|
use crate::xml::multistatus::{PropstatElement, PropstatWrapper, ResponseElement};
|
||||||
@@ -74,8 +77,30 @@ pub(crate) async fn actix_route_proppatch<R: ResourceService>(
|
|||||||
route_proppatch(
|
route_proppatch(
|
||||||
&path.into_inner(),
|
&path.into_inner(),
|
||||||
req.path(),
|
req.path(),
|
||||||
body,
|
&body,
|
||||||
principal,
|
&principal,
|
||||||
|
resource_service.as_ref(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
pub(crate) async fn axum_route_proppatch<R: ResourceService>(
|
||||||
|
Path(path): Path<R::PathComponents>,
|
||||||
|
State(resource_service): State<Arc<R>>,
|
||||||
|
Extension(principal): Extension<R::Principal>,
|
||||||
|
uri: OriginalUri,
|
||||||
|
body: String,
|
||||||
|
) -> Result<
|
||||||
|
MultistatusElement<String, String>,
|
||||||
|
R::Error,
|
||||||
|
> {
|
||||||
|
route_proppatch(
|
||||||
|
&path,
|
||||||
|
uri.path(),
|
||||||
|
&body,
|
||||||
|
&principal,
|
||||||
resource_service.as_ref(),
|
resource_service.as_ref(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -84,8 +109,8 @@ pub(crate) async fn actix_route_proppatch<R: ResourceService>(
|
|||||||
pub(crate) async fn route_proppatch<R: ResourceService>(
|
pub(crate) async fn route_proppatch<R: ResourceService>(
|
||||||
path_components: &R::PathComponents,
|
path_components: &R::PathComponents,
|
||||||
path: &str,
|
path: &str,
|
||||||
body: String,
|
body: &str,
|
||||||
principal: R::Principal,
|
principal: &R::Principal,
|
||||||
resource_service: &R,
|
resource_service: &R,
|
||||||
) -> Result<MultistatusElement<String, String>, R::Error> {
|
) -> Result<MultistatusElement<String, String>, R::Error> {
|
||||||
let href = path.to_owned();
|
let href = path.to_owned();
|
||||||
|
|||||||
@@ -12,12 +12,19 @@ use rustical_xml::{EnumVariants, NamespaceOwned, PropName, XmlDeserialize, XmlSe
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
mod axum_methods;
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
mod axum_service;
|
||||||
mod methods;
|
mod methods;
|
||||||
mod principal_uri;
|
mod principal_uri;
|
||||||
mod resource_service;
|
mod resource_service;
|
||||||
|
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
pub use axum_methods::AxumMethods;
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
pub use axum_service::AxumService;
|
||||||
pub use principal_uri::PrincipalUri;
|
pub use principal_uri::PrincipalUri;
|
||||||
pub use resource_service::*;
|
|
||||||
|
|
||||||
pub trait ResourceProp: XmlSerialize + XmlDeserialize {}
|
pub trait ResourceProp: XmlSerialize + XmlDeserialize {}
|
||||||
impl<T: XmlSerialize + XmlDeserialize> ResourceProp for T {}
|
impl<T: XmlSerialize + XmlDeserialize> ResourceProp for T {}
|
||||||
@@ -25,8 +32,8 @@ impl<T: XmlSerialize + XmlDeserialize> ResourceProp for T {}
|
|||||||
pub trait ResourcePropName: FromStr {}
|
pub trait ResourcePropName: FromStr {}
|
||||||
impl<T: FromStr> ResourcePropName for T {}
|
impl<T: FromStr> ResourcePropName for T {}
|
||||||
|
|
||||||
pub trait Resource: Clone + 'static {
|
pub trait Resource: Clone + Send + 'static {
|
||||||
type Prop: ResourceProp + PartialEq + Clone + EnumVariants + PropName;
|
type Prop: ResourceProp + PartialEq + Clone + EnumVariants + PropName + Send;
|
||||||
type Error: From<crate::Error>;
|
type Error: From<crate::Error>;
|
||||||
type Principal: Principal;
|
type Principal: Principal;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
pub trait PrincipalUri: 'static {
|
pub trait PrincipalUri: 'static + Clone + Send + Sync {
|
||||||
fn principal_uri(&self, principal: &str) -> String;
|
fn principal_uri(&self, principal: &str) -> String;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,28 +2,37 @@
|
|||||||
use super::methods::{actix_route_delete, actix_route_propfind, actix_route_proppatch};
|
use super::methods::{actix_route_delete, actix_route_propfind, actix_route_proppatch};
|
||||||
use super::{PrincipalUri, Resource};
|
use super::{PrincipalUri, Resource};
|
||||||
use crate::Principal;
|
use crate::Principal;
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
use crate::resource::{AxumMethods, AxumService};
|
||||||
#[cfg(feature = "actix")]
|
#[cfg(feature = "actix")]
|
||||||
use actix_web::{http::Method, web, web::Data};
|
use actix_web::{http::Method, web, web::Data};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::str::FromStr;
|
use std::{str::FromStr, sync::Arc};
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait]
|
||||||
pub trait ResourceService: Sized + 'static {
|
pub trait ResourceService: Sized + Send + Sync + 'static {
|
||||||
|
type PathComponents: for<'de> Deserialize<'de> + Sized + Send + Sync + Clone + 'static; // defines how the resource URI maps to parameters, i.e. /{principal}/{calendar} -> (String, String)
|
||||||
type MemberType: Resource<Error = Self::Error, Principal = Self::Principal>;
|
type MemberType: Resource<Error = Self::Error, Principal = Self::Principal>;
|
||||||
type PathComponents: for<'de> Deserialize<'de> + Sized + Clone + 'static; // defines how the resource URI maps to parameters, i.e. /{principal}/{calendar} -> (String, String)
|
|
||||||
type Resource: Resource<Error = Self::Error, Principal = Self::Principal>;
|
type Resource: Resource<Error = Self::Error, Principal = Self::Principal>;
|
||||||
type Error: From<crate::Error>;
|
type Error: From<crate::Error> + Send;
|
||||||
type Principal: Principal;
|
type Principal: Principal;
|
||||||
type PrincipalUri: PrincipalUri;
|
type PrincipalUri: PrincipalUri;
|
||||||
|
|
||||||
|
const DAV_HEADER: &'static str;
|
||||||
|
|
||||||
async fn get_members(
|
async fn get_members(
|
||||||
&self,
|
&self,
|
||||||
_path_components: &Self::PathComponents,
|
_path: &Self::PathComponents,
|
||||||
) -> Result<Vec<(String, Self::MemberType)>, Self::Error> {
|
) -> Result<Vec<(String, Self::MemberType)>, Self::Error> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn test_get_members(&self, _path: &Self::PathComponents) -> Result<String, Self::Error> {
|
||||||
|
// ) -> Result<Vec<Self::MemberType>, Self::Error> {
|
||||||
|
Ok("asd".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_resource(
|
async fn get_resource(
|
||||||
&self,
|
&self,
|
||||||
_path: &Self::PathComponents,
|
_path: &Self::PathComponents,
|
||||||
@@ -69,4 +78,12 @@ pub trait ResourceService: Sized + 'static {
|
|||||||
where
|
where
|
||||||
Self::Error: actix_web::ResponseError,
|
Self::Error: actix_web::ResponseError,
|
||||||
Self::Principal: actix_web::FromRequest;
|
Self::Principal: actix_web::FromRequest;
|
||||||
|
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
fn axum_service(self) -> AxumService<Self>
|
||||||
|
where
|
||||||
|
Self: Clone + Send + Sync + AxumMethods,
|
||||||
|
{
|
||||||
|
AxumService::new(Arc::new(self))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ impl<PRS: ResourceService + Clone, P: Principal, PURI: PrincipalUri>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait]
|
||||||
impl<PRS: ResourceService<Principal = P> + Clone, P: Principal, PURI: PrincipalUri> ResourceService
|
impl<PRS: ResourceService<Principal = P> + Clone, P: Principal, PURI: PrincipalUri> ResourceService
|
||||||
for RootResourceService<PRS, P, PURI>
|
for RootResourceService<PRS, P, PURI>
|
||||||
{
|
{
|
||||||
@@ -69,6 +69,8 @@ impl<PRS: ResourceService<Principal = P> + Clone, P: Principal, PURI: PrincipalU
|
|||||||
type Principal = P;
|
type Principal = P;
|
||||||
type PrincipalUri = PURI;
|
type PrincipalUri = PURI;
|
||||||
|
|
||||||
|
const DAV_HEADER: &str = "1, 3, access-control";
|
||||||
|
|
||||||
async fn get_resource(&self, _: &()) -> Result<Self::Resource, Self::Error> {
|
async fn get_resource(&self, _: &()) -> Result<Self::Resource, Self::Error> {
|
||||||
Ok(RootResource::<PRS::Resource, P>::default())
|
Ok(RootResource::<PRS::Resource, P>::default())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,3 +124,25 @@ impl<T1: XmlSerialize, T2: XmlSerialize> Responder for MultistatusElement<T1, T2
|
|||||||
.body(String::from_utf8(output).unwrap())
|
.body(String::from_utf8(output).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
impl<T1: XmlSerialize, T2: XmlSerialize> axum::response::IntoResponse
|
||||||
|
for MultistatusElement<T1, T2>
|
||||||
|
{
|
||||||
|
fn into_response(self) -> axum::response::Response {
|
||||||
|
use axum::body::Body;
|
||||||
|
use http::header;
|
||||||
|
|
||||||
|
let mut output: Vec<_> = b"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".into();
|
||||||
|
let mut writer = quick_xml::Writer::new_with_indent(&mut output, b' ', 4);
|
||||||
|
if let Err(err) = self.serialize_root(&mut writer) {
|
||||||
|
return crate::Error::from(err).into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut resp = axum::response::Response::builder().status(StatusCode::MULTI_STATUS);
|
||||||
|
resp.headers_mut()
|
||||||
|
.unwrap()
|
||||||
|
.insert(header::CONTENT_TYPE, "application/xml".try_into().unwrap());
|
||||||
|
resp.body(Body::from(output)).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,3 +23,4 @@ reqwest.workspace = true
|
|||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
rustical_dav.workspace = true
|
rustical_dav.workspace = true
|
||||||
rustical_store.workspace = true
|
rustical_store.workspace = true
|
||||||
|
http.workspace = true
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use actix_web::http::StatusCode;
|
use http::StatusCode;
|
||||||
use reqwest::{
|
use reqwest::{
|
||||||
Method, Request,
|
Method, Request,
|
||||||
header::{self, HeaderName, HeaderValue},
|
header::{self, HeaderName, HeaderValue},
|
||||||
|
|||||||
@@ -335,7 +335,7 @@ pub fn configure_frontend<AP: AuthenticationProvider, CS: CalendarStore, AS: Add
|
|||||||
|
|
||||||
struct OidcUserStore<AP: AuthenticationProvider>(Arc<AP>);
|
struct OidcUserStore<AP: AuthenticationProvider>(Arc<AP>);
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait]
|
||||||
impl<AP: AuthenticationProvider> UserStore for OidcUserStore<AP> {
|
impl<AP: AuthenticationProvider> UserStore for OidcUserStore<AP> {
|
||||||
type Error = rustical_store::Error;
|
type Error = rustical_store::Error;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use actix_web::ResponseError;
|
use actix_web::ResponseError;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait]
|
||||||
pub trait UserStore: 'static {
|
pub trait UserStore: 'static {
|
||||||
type Error: ResponseError;
|
type Error: ResponseError;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::error::Error;
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait AuthenticationProvider: 'static {
|
pub trait AuthenticationProvider: Send + Sync + 'static {
|
||||||
async fn get_principals(&self) -> Result<Vec<User>, crate::Error>;
|
async fn get_principals(&self) -> Result<Vec<User>, crate::Error>;
|
||||||
async fn get_principal(&self, id: &str) -> Result<Option<User>, crate::Error>;
|
async fn get_principal(&self, id: &str) -> Result<Option<User>, crate::Error>;
|
||||||
async fn remove_principal(&self, id: &str) -> Result<(), crate::Error>;
|
async fn remove_principal(&self, id: &str) -> Result<(), crate::Error>;
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ pub trait EnumVariants {
|
|||||||
pub trait PropName: Sized {
|
pub trait PropName: Sized {
|
||||||
type Names: Into<(Option<Namespace<'static>>, &'static str)>
|
type Names: Into<(Option<Namespace<'static>>, &'static str)>
|
||||||
+ Clone
|
+ Clone
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
+ From<Self>
|
+ From<Self>
|
||||||
+ FromStr<Err: std::fmt::Debug>
|
+ FromStr<Err: std::fmt::Debug>
|
||||||
+ Hash
|
+ Hash
|
||||||
|
|||||||
Reference in New Issue
Block a user