diff --git a/crates/caldav/src/calendar_object/methods.rs b/crates/caldav/src/calendar_object/methods.rs index 2d7fc3f..3044aa7 100644 --- a/crates/caldav/src/calendar_object/methods.rs +++ b/crates/caldav/src/calendar_object/methods.rs @@ -11,7 +11,7 @@ use rustical_ical::CalendarObject; use rustical_store::CalendarStore; use rustical_store::auth::Principal; use std::str::FromStr; -use tracing::{debug, instrument}; +use tracing::{instrument, warn}; #[instrument(skip(cal_store))] pub async fn get_event( @@ -94,9 +94,13 @@ pub async fn put_event( true }; - let Ok(object) = CalendarObject::from_ics(body.clone()) else { - debug!("invalid calendar data:\n{body}"); - return Err(Error::PreconditionFailed(Precondition::ValidCalendarData)); + let object = match CalendarObject::from_ics(body.clone()) { + Ok(object) => object, + Err(err) => { + warn!("invalid calendar data:\n{body}"); + warn!("{err:#?}"); + return Err(Error::PreconditionFailed(Precondition::ValidCalendarData)); + } }; let etag = object.get_etag(); cal_store diff --git a/crates/caldav/src/error.rs b/crates/caldav/src/error.rs index 51e3da5..3fc5530 100644 --- a/crates/caldav/src/error.rs +++ b/crates/caldav/src/error.rs @@ -79,6 +79,9 @@ impl Error { impl IntoResponse for Error { fn into_response(self) -> axum::response::Response { + if let Self::PreconditionFailed(precondition) = self { + return precondition.into_response(); + } if matches!( self.status_code(), StatusCode::INTERNAL_SERVER_ERROR | StatusCode::PRECONDITION_FAILED diff --git a/src/integration_tests/caldav/calendar.rs b/src/integration_tests/caldav/calendar.rs index 475fe1d..ae3e005 100644 --- a/src/integration_tests/caldav/calendar.rs +++ b/src/integration_tests/caldav/calendar.rs @@ -8,7 +8,7 @@ use rustical_store::{CalendarMetadata, CalendarStore}; use rustical_store_sqlite::tests::{TestStoreContext, test_store_context}; use tower::ServiceExt; -fn mkcalendar_template( +pub fn mkcalendar_template( CalendarMetadata { displayname, order: _order, diff --git a/src/integration_tests/caldav/calendar_put.rs b/src/integration_tests/caldav/calendar_put.rs new file mode 100644 index 0000000..b99f274 --- /dev/null +++ b/src/integration_tests/caldav/calendar_put.rs @@ -0,0 +1,77 @@ +use axum::body::Body; +use headers::{Authorization, HeaderMapExt}; +use http::{Request, StatusCode}; +use rstest::rstest; +use rustical_store::CalendarMetadata; +use rustical_store_sqlite::tests::{TestStoreContext, test_store_context}; +use tower::ServiceExt; + +use crate::integration_tests::{ + ResponseExtractString, caldav::calendar::mkcalendar_template, get_app, +}; + +#[rstest] +#[tokio::test] +async fn test_put_invalid( + #[from(test_store_context)] + #[future] + context: TestStoreContext, +) { + let context = context.await; + let app = get_app(context.clone()); + + let calendar_meta = CalendarMetadata { + displayname: Some("Calendar".to_string()), + description: Some("Description".to_string()), + color: Some("#00FF00".to_string()), + order: 0, + }; + let (principal, cal_id) = ("user", "calendar"); + let url = format!("/caldav/principal/{principal}/{cal_id}"); + + let mut request = Request::builder() + .method("MKCALENDAR") + .uri(&url) + .body(Body::from(mkcalendar_template(&calendar_meta))) + .unwrap(); + request + .headers_mut() + .typed_insert(Authorization::basic("user", "pass")); + let response = app.clone().oneshot(request).await.unwrap(); + assert_eq!(response.status(), StatusCode::CREATED); + + // Invalid calendar data + let ical = r"BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Example Corp.//CalDAV Client//EN +BEGIN:VEVENT +UID:20010712T182145Z-123401@example.com +DTSTAMP:20060712T182145Z +DTSTART:20060714T170000Z +RRULE:UNTIL=123 +DTEND:20060715T040000Z +SUMMARY:Bastille Day Party +END:VEVENT +END:VCALENDAR"; + + let mut request = Request::builder() + .method("PUT") + .uri(format!("{url}/qwue23489.ics")) + .header("If-None-Match", "*") + .header("Content-Type", "text/calendar") + .body(Body::from(ical)) + .unwrap(); + request + .headers_mut() + .typed_insert(Authorization::basic("user", "pass")); + + let response = app.clone().oneshot(request).await.unwrap(); + assert_eq!(response.status(), StatusCode::PRECONDITION_FAILED); + let body = response.extract_string().await; + insta::assert_snapshot!(body, @r#" + + + + + "#); +} diff --git a/src/integration_tests/caldav/mod.rs b/src/integration_tests/caldav/mod.rs index 4a91cd7..5d7d532 100644 --- a/src/integration_tests/caldav/mod.rs +++ b/src/integration_tests/caldav/mod.rs @@ -9,6 +9,7 @@ use tower::ServiceExt; mod calendar; mod calendar_import; +mod calendar_put; mod calendar_report; #[rstest]