From d14ded71791f5001e2846276f373b3c0db111f1d Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Sat, 10 May 2025 11:37:28 +0200 Subject: [PATCH] Put OPTIONS handler into dedicated function --- crates/caldav/src/lib.rs | 132 +++++++++++++++++++++++--------------- crates/carddav/src/lib.rs | 52 ++++++++------- 2 files changed, 109 insertions(+), 75 deletions(-) diff --git a/crates/caldav/src/lib.rs b/crates/caldav/src/lib.rs index b574300..00a76fe 100644 --- a/crates/caldav/src/lib.rs +++ b/crates/caldav/src/lib.rs @@ -1,4 +1,5 @@ use actix_web::HttpResponse; +use actix_web::body::BoxBody; use actix_web::dev::{HttpServiceFactory, ServiceResponse}; use actix_web::http::header::{self, HeaderName, HeaderValue}; use actix_web::http::{Method, StatusCode}; @@ -24,6 +25,32 @@ mod subscription; pub use error::Error; +/// Quite a janky implementation but the default METHOD_NOT_ALLOWED response gives us the allowed +/// methods of a resource +fn options_handler() -> ErrorHandlers { + ErrorHandlers::new().handler(StatusCode::METHOD_NOT_ALLOWED, |res| { + Ok(ErrorHandlerResponse::Response( + if res.request().method() == Method::OPTIONS { + let mut response = HttpResponse::Ok(); + response.insert_header(( + HeaderName::from_static("dav"), + // https://datatracker.ietf.org/doc/html/rfc4918#section-18 + HeaderValue::from_static( + "1, 3, access-control, calendar-access, extended-mkcol, calendar-no-timezone, webdav-push", + ), + )); + + if let Some(allow) = res.headers().get(header::ALLOW) { + response.insert_header((header::ALLOW, allow.to_owned())); + } + ServiceResponse::new(res.into_parts().0, response.finish()).map_into_right_body() + } else { + res.map_into_left_body() + }, + )) + }) +} + pub fn caldav_service< AP: AuthenticationProvider, AS: AddressbookStore, @@ -38,62 +65,65 @@ pub fn caldav_service< let birthday_store = Arc::new(ContactBirthdayStore::new(addr_store)); web::scope("") - .wrap(AuthenticationMiddleware::new(auth_provider.clone())) - .wrap( - ErrorHandlers::new().handler(StatusCode::METHOD_NOT_ALLOWED, |res| { - Ok(ErrorHandlerResponse::Response( - if res.request().method() == Method::OPTIONS { - let mut response = HttpResponse::Ok(); - response.insert_header(( - HeaderName::from_static("dav"), - // https://datatracker.ietf.org/doc/html/rfc4918#section-18 - HeaderValue::from_static( - "1, 3, access-control, calendar-access, extended-mkcol, calendar-no-timezone, webdav-push", - ), - )); - - if let Some(allow) = res.headers().get(header::ALLOW) { - response.insert_header((header::ALLOW, allow.to_owned())); - } - ServiceResponse::new(res.into_parts().0, response.finish()).map_into_right_body() - } else { - res.map_into_left_body() - }, - )) - }), - ) - .app_data(Data::from(store.clone())) - .app_data(Data::from(birthday_store.clone())) - .app_data(Data::from(subscription_store)) - .service(RootResourceService::::default().actix_resource()) - .service( - web::scope("/principal").service( - web::scope("/{principal}") - .service(PrincipalResourceService{auth_provider, home_set: &[ - ("calendar", false), ("birthdays", true) - ]}.actix_resource().name(PrincipalResource::route_name())) - .service(web::scope("/calendar") - .service(CalendarSetResourceService::new(store.clone()).actix_resource()) + .wrap(AuthenticationMiddleware::new(auth_provider.clone())) + .wrap(options_handler()) + .app_data(Data::from(store.clone())) + .app_data(Data::from(birthday_store.clone())) + .app_data(Data::from(subscription_store)) + .service(RootResourceService::::default().actix_resource()) + .service( + web::scope("/principal").service( + web::scope("/{principal}") + .service( + PrincipalResourceService { + auth_provider, + home_set: &[("calendar", false), ("birthdays", true)], + } + .actix_resource() + .name(PrincipalResource::route_name()), + ) + .service( + web::scope("/calendar") + .service( + CalendarSetResourceService::new(store.clone()).actix_resource(), + ) .service( web::scope("/{calendar_id}") - .service( - ResourceServiceRoute(CalendarResourceService::<_, S>::new(store.clone())) - ) - .service(web::scope("/{object_id}.ics").service(CalendarObjectResourceService::new(store.clone()).actix_resource() + .service(ResourceServiceRoute( + CalendarResourceService::<_, S>::new(store.clone()), )) + .service( + web::scope("/{object_id}.ics").service( + CalendarObjectResourceService::new(store.clone()) + .actix_resource(), + ), + ), + ), + ) + .service( + web::scope("/birthdays") + .service( + CalendarSetResourceService::new(birthday_store.clone()) + .actix_resource(), ) - ) - .service(web::scope("/birthdays") - .service(CalendarSetResourceService::new(birthday_store.clone()).actix_resource()) .service( web::scope("/{calendar_id}") - .service( - ResourceServiceRoute(CalendarResourceService::<_, S>::new(birthday_store.clone())) - ) - .service(web::scope("/{object_id}.ics").service(CalendarObjectResourceService::new(birthday_store.clone()).actix_resource() + .service(ResourceServiceRoute( + CalendarResourceService::<_, S>::new( + birthday_store.clone(), + ), )) - ) - ) - ), - ).service(subscription_resource::()) + .service( + web::scope("/{object_id}.ics").service( + CalendarObjectResourceService::new( + birthday_store.clone(), + ) + .actix_resource(), + ), + ), + ), + ), + ), + ) + .service(subscription_resource::()) } diff --git a/crates/carddav/src/lib.rs b/crates/carddav/src/lib.rs index f4adc46..879d5bd 100644 --- a/crates/carddav/src/lib.rs +++ b/crates/carddav/src/lib.rs @@ -1,5 +1,6 @@ use actix_web::{ HttpResponse, + body::BoxBody, dev::{HttpServiceFactory, ServiceResponse}, http::{ Method, StatusCode, @@ -25,6 +26,32 @@ pub mod addressbook; pub mod error; pub mod principal; +/// Quite a janky implementation but the default METHOD_NOT_ALLOWED response gives us the allowed +/// methods of a resource +fn options_handler() -> ErrorHandlers { + ErrorHandlers::new().handler(StatusCode::METHOD_NOT_ALLOWED, |res| { + Ok(ErrorHandlerResponse::Response( + if res.request().method() == Method::OPTIONS { + let mut response = HttpResponse::Ok(); + response.insert_header(( + HeaderName::from_static("dav"), + // https://datatracker.ietf.org/doc/html/rfc4918#section-18 + HeaderValue::from_static( + "1, 3, access-control, addressbook, extended-mkcol, webdav-push", + ), + )); + + if let Some(allow) = res.headers().get(header::ALLOW) { + response.insert_header((header::ALLOW, allow.to_owned())); + } + ServiceResponse::new(res.into_parts().0, response.finish()).map_into_right_body() + } else { + res.map_into_left_body() + }, + )) + }) +} + pub fn carddav_service( auth_provider: Arc, store: Arc, @@ -32,30 +59,7 @@ pub fn carddav_service impl HttpServiceFactory { web::scope("") .wrap(AuthenticationMiddleware::new(auth_provider.clone())) - .wrap( - ErrorHandlers::new().handler(StatusCode::METHOD_NOT_ALLOWED, |res| { - Ok(ErrorHandlerResponse::Response( - if res.request().method() == Method::OPTIONS { - let mut response = HttpResponse::Ok(); - response.insert_header(( - HeaderName::from_static("dav"), - // https://datatracker.ietf.org/doc/html/rfc4918#section-18 - HeaderValue::from_static( - "1, 3, access-control, addressbook, extended-mkcol, webdav-push", - ), - )); - - if let Some(allow) = res.headers().get(header::ALLOW) { - response.insert_header((header::ALLOW, allow.to_owned())); - } - ServiceResponse::new(res.into_parts().0, response.finish()) - .map_into_right_body() - } else { - res.map_into_left_body() - }, - )) - }), - ) + .wrap(options_handler()) .app_data(Data::from(store.clone())) .app_data(Data::from(subscription_store)) .service(RootResourceService::::default().actix_resource())