Skip to main content

rustical_caldav/
lib.rs

1#![warn(clippy::all, clippy::pedantic, clippy::nursery)]
2#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)]
3use axum::{Extension, Router};
4use derive_more::Constructor;
5use http::Uri;
6use principal::PrincipalResourceService;
7use rustical_dav::resource::{PrincipalUri, ResourceService};
8use rustical_dav::resources::RootResourceService;
9use rustical_dav::rfc_3986_percent_encode;
10use rustical_store::auth::middleware::AuthenticationLayer;
11use rustical_store::auth::{AuthenticationProvider, Principal};
12use rustical_store::{CalendarStore, SubscriptionStore};
13use serde::{Deserialize, Serialize};
14use std::sync::Arc;
15
16pub mod calendar;
17pub mod calendar_object;
18pub mod error;
19pub mod principal;
20pub use error::Error;
21
22#[derive(Debug, Clone, Constructor)]
23pub struct CalDavPrincipalUri(&'static str);
24
25impl PrincipalUri for CalDavPrincipalUri {
26    fn principal_collection(&self) -> Uri {
27        Uri::builder()
28            .path_and_query(format!("{}/principal/", self.0))
29            .build()
30            .unwrap()
31    }
32    fn principal_uri(&self, principal: &str) -> Uri {
33        let principal = rfc_3986_percent_encode(principal);
34        Uri::builder()
35            .path_and_query(format!("{}{}/", self.principal_collection(), principal))
36            .build()
37            .unwrap()
38    }
39}
40
41pub fn caldav_router<AP: AuthenticationProvider, C: CalendarStore, S: SubscriptionStore>(
42    prefix: &'static str,
43    auth_provider: Arc<AP>,
44    store: Arc<C>,
45    subscription_store: Arc<S>,
46    simplified_home_set: bool,
47    config: Arc<CalDavConfig>,
48) -> Router {
49    Router::new().nest(
50        prefix,
51        RootResourceService::<_, Principal, CalDavPrincipalUri>::new(PrincipalResourceService {
52            auth_provider: auth_provider.clone(),
53            sub_store: subscription_store,
54            cal_store: store,
55            simplified_home_set,
56            config,
57        })
58        .axum_router()
59        .layer(AuthenticationLayer::new(auth_provider))
60        .layer(Extension(CalDavPrincipalUri(prefix))),
61    )
62}
63
64const fn default_true() -> bool {
65    true
66}
67
68#[derive(Debug, Clone, Deserialize, Serialize)]
69#[serde(deny_unknown_fields, default)]
70pub struct CalDavConfig {
71    #[serde(default = "default_true")]
72    rfc7809: bool,
73}
74
75impl Default for CalDavConfig {
76    fn default() -> Self {
77        Self { rfc7809: true }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use crate::CalDavPrincipalUri;
84    use rustical_dav::resource::PrincipalUri;
85
86    #[rstest::rstest]
87    #[case("user", "/caldav/principal/user/")]
88    #[case("user with space", "/caldav/principal/user%20with%20space/")]
89    #[case("asd@asd.de", "/caldav/principal/asd%40asd.de/")]
90    fn test_principal_uri_encoding(#[case] principal: &str, #[case] output: &str) {
91        assert_eq!(
92            CalDavPrincipalUri("/caldav").principal_uri(principal),
93            output
94        );
95    }
96}