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}