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