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}