Skip to content

Commit ce0d512

Browse files
Add CalDAV support (#48)
* Add CalDAV support * CI: Check build with caldav * semver * Fix davheaders.rs typos Co-authored-by: Petr Ermishkin <[email protected]> * cargo clippy * cargo fmt --------- Co-authored-by: Petr Ermishkin <[email protected]>
1 parent a46e3b0 commit ce0d512

File tree

16 files changed

+1769
-19
lines changed

16 files changed

+1769
-19
lines changed

.github/workflows/CI.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ jobs:
5151
with:
5252
command: build
5353
args: --features actix-compat
54+
- name: Check build with caldav
55+
uses: actions-rs/cargo@v1
56+
with:
57+
command: build
58+
args: --features caldav
5459
- name: Test
5560
uses: actions-rs/cargo@v1
5661
with:

Cargo.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "dav-server"
3-
version = "0.8.0"
3+
version = "0.9.0"
44
readme = "README.md"
55
description = "Rust WebDAV server library. A fork of the webdav-handler crate."
66
repository = "https://github.com/messense/dav-server-rs"
@@ -21,7 +21,8 @@ features = ["full"]
2121
default = ["localfs", "memfs"]
2222
actix-compat = [ "actix-web" ]
2323
warp-compat = [ "warp", "hyper" ]
24-
all = [ "actix-compat", "warp-compat" ]
24+
caldav = [ "icalendar", "chrono" ]
25+
all = [ "actix-compat", "warp-compat", "caldav" ]
2526
localfs = ["libc", "lru", "parking_lot", "reflink-copy"]
2627
memfs = ["libc"]
2728

@@ -33,6 +34,10 @@ required-features = [ "actix-compat" ]
3334
name = "warp"
3435
required-features = [ "warp-compat" ]
3536

37+
[[example]]
38+
name = "caldav"
39+
required-features = [ "caldav" ]
40+
3641
[dependencies]
3742
bytes = "1.0.1"
3843
dyn-clone = "1"
@@ -64,6 +69,8 @@ hyper = { version = "1.1.0", optional = true }
6469
warp = { version = "0.3.0", optional = true, default-features = false }
6570
actix-web = { version = "4.0.0-beta.15", optional = true }
6671
reflink-copy = { version = "0.1.14", optional = true }
72+
icalendar = { version = "0.17.1", optional = true }
73+
chrono = { version = "0.4", optional = true }
6774
derive-where = "1.6.0"
6875

6976
[dev-dependencies]

README.CalDAV.md

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
# CalDAV Support in dav-server
2+
3+
This document describes the CalDAV (Calendaring Extensions to WebDAV) support in the dav-server library.
4+
5+
## Overview
6+
7+
CalDAV is an extension of WebDAV that provides a standard way to access and manage calendar data over HTTP. It's defined in [RFC 4791](https://tools.ietf.org/html/rfc4791) and allows calendar clients to:
8+
9+
- Create and manage calendar collections
10+
- Store and retrieve calendar events, tasks, and journals
11+
- Query calendars with complex filters
12+
- Synchronize calendar data between clients and servers
13+
14+
## Features
15+
16+
The CalDAV implementation in dav-server includes:
17+
18+
- **Calendar Collections**: Special WebDAV collections that contain calendar data
19+
- **MKCALENDAR Method**: Create new calendar collections
20+
- **REPORT Method**: Query calendar data with filters
21+
- **CalDAV Properties**: Calendar-specific WebDAV properties
22+
- **iCalendar Support**: Parse and validate iCalendar data
23+
- **Time Range Queries**: Filter events by date/time ranges
24+
- **Component Filtering**: Filter by calendar component types (VEVENT, VTODO, etc.)
25+
26+
## Enabling CalDAV
27+
28+
CalDAV support is available as an optional cargo feature:
29+
30+
```toml
31+
[dependencies]
32+
dav-server = { version = "0.8", features = ["caldav"] }
33+
```
34+
35+
This adds the following dependencies:
36+
- `icalendar`: For parsing and validating iCalendar data
37+
- `chrono`: For date/time handling
38+
39+
## Quick Start
40+
41+
Here's a basic CalDAV server setup:
42+
43+
```rust
44+
use dav_server::{DavHandler, fakels::FakeLs, localfs::LocalFs};
45+
46+
let server = DavHandler::builder()
47+
.filesystem(LocalFs::new("/calendars", false, false, false))
48+
.locksystem(FakeLs::new())
49+
.build_handler();
50+
```
51+
52+
## CalDAV Methods
53+
54+
### MKCALENDAR
55+
56+
Creates a new calendar collection:
57+
58+
```bash
59+
curl -X MKCALENDAR http://localhost:8080/my-calendar/
60+
```
61+
62+
With properties:
63+
64+
```bash
65+
curl -X MKCALENDAR http://localhost:8080/my-calendar/ \
66+
-H "Content-Type: application/xml" \
67+
--data '<?xml version="1.0" encoding="utf-8" ?>
68+
<C:mkcalendar xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
69+
<D:set>
70+
<D:prop>
71+
<D:displayname>My Calendar</D:displayname>
72+
<C:calendar-description>Personal calendar</C:calendar-description>
73+
</D:prop>
74+
</D:set>
75+
</C:mkcalendar>'
76+
```
77+
78+
### REPORT
79+
80+
Query calendar data:
81+
82+
#### Calendar Query
83+
84+
```bash
85+
curl -X REPORT http://localhost:8080/my-calendar/ \
86+
-H "Content-Type: application/xml" \
87+
-H "Depth: 1" \
88+
--data '<?xml version="1.0" encoding="utf-8" ?>
89+
<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
90+
<D:prop>
91+
<C:calendar-data/>
92+
</D:prop>
93+
<C:filter>
94+
<C:comp-filter name="VCALENDAR">
95+
<C:comp-filter name="VEVENT">
96+
<C:time-range start="20240101T000000Z" end="20241231T235959Z"/>
97+
</C:comp-filter>
98+
</C:comp-filter>
99+
</C:filter>
100+
</C:calendar-query>'
101+
```
102+
103+
#### Calendar Multiget
104+
105+
```bash
106+
curl -X REPORT http://localhost:8080/my-calendar/ \
107+
-H "Content-Type: application/xml" \
108+
--data '<?xml version="1.0" encoding="utf-8" ?>
109+
<C:calendar-multiget xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
110+
<D:prop>
111+
<C:calendar-data/>
112+
</D:prop>
113+
<D:href>/my-calendar/event1.ics</D:href>
114+
<D:href>/my-calendar/event2.ics</D:href>
115+
</C:calendar-multiget>'
116+
```
117+
118+
## CalDAV Properties
119+
120+
The implementation supports standard CalDAV properties:
121+
122+
### Collection Properties
123+
124+
- `calendar-description`: Human-readable description
125+
- `calendar-timezone`: Default timezone for the calendar
126+
- `supported-calendar-component-set`: Supported component types (VEVENT, VTODO, etc.)
127+
- `supported-calendar-data`: Supported calendar data formats
128+
- `max-resource-size`: Maximum size for calendar resources
129+
130+
### Principal Properties
131+
132+
- `calendar-home-set`: URL of the user's calendar home collection
133+
- `calendar-user-address-set`: Calendar user's addresses
134+
- `schedule-inbox-URL`: URL for scheduling messages
135+
- `schedule-outbox-URL`: URL for outgoing scheduling
136+
137+
## Working with Calendar Data
138+
139+
### Adding Events
140+
141+
Store iCalendar data using PUT:
142+
143+
```bash
144+
curl -X PUT http://localhost:8080/my-calendar/event.ics \
145+
-H "Content-Type: text/calendar" \
146+
--data 'BEGIN:VCALENDAR
147+
VERSION:2.0
148+
PRODID:-//Example Corp//CalDAV Client//EN
149+
BEGIN:VEVENT
150+
151+
DTSTART:20240101T120000Z
152+
DTEND:20240101T130000Z
153+
SUMMARY:New Year Meeting
154+
DESCRIPTION:Planning meeting for the new year
155+
END:VEVENT
156+
END:VCALENDAR'
157+
```
158+
159+
### Retrieving Events
160+
161+
Use GET to retrieve individual calendar resources:
162+
163+
```bash
164+
curl http://localhost:8080/my-calendar/event.ics
165+
```
166+
167+
## Client Compatibility
168+
169+
The CalDAV implementation has been tested with:
170+
171+
- **Thunderbird**: Full support for calendar sync
172+
- **Apple Calendar**: Compatible with basic operations
173+
- **CalDAV-Sync (Android)**: Works with standard CalDAV features
174+
- **Evolution**: Support for calendar collections and events
175+
176+
## Limitations
177+
178+
Current limitations include:
179+
180+
- No scheduling support (iTIP/iMIP)
181+
- Limited calendar-user-principal support
182+
- No calendar sharing or ACL support
183+
- Basic time zone handling
184+
- No recurring event expansion in queries
185+
186+
## Example Applications
187+
These calendar server examples lacks authentication and does not support user-specific access.
188+
For a production environment, you should implement the GuardedFileSystem for better security and user management.
189+
190+
### Calendar Server
191+
192+
```rust
193+
use dav_server::{DavHandler, fakels::FakeLs, localfs::LocalFs};
194+
use std::net::SocketAddr;
195+
196+
#[tokio::main]
197+
async fn main() {
198+
let server = DavHandler::builder()
199+
.filesystem(LocalFs::new("/calendars", false, false, false))
200+
.locksystem(FakeLs::new())
201+
// Use .strip_prefix if you want to start the handler with a prefix path like "/calendars".
202+
// None will start with root ("/")
203+
// .strip_prefix("/calendars")
204+
.build_handler();
205+
206+
// Serve on port 8080
207+
// Calendars accessible at http://localhost:8080/calendars/
208+
}
209+
```
210+
211+
### Multi-tenant Calendar Service
212+
213+
```rust
214+
use dav_server::{DavHandler, memfs::MemFs, memls::MemLs};
215+
216+
// Use in-memory filesystem for demonstration
217+
let server = DavHandler::builder()
218+
.filesystem(MemFs::new())
219+
.locksystem(MemLs::new())
220+
.principal("/principals/user1/")
221+
.build_handler();
222+
```
223+
224+
## Testing
225+
226+
Run CalDAV tests with:
227+
228+
```bash
229+
cargo test --features caldav caldav_tests
230+
```
231+
232+
Run the CalDAV example:
233+
234+
```bash
235+
cargo run --example caldav --features caldav
236+
```
237+
238+
## Standards Compliance
239+
240+
This implementation follows:
241+
242+
- [RFC 4791](https://tools.ietf.org/html/rfc4791) - Calendaring Extensions to WebDAV (CalDAV)
243+
- [RFC 5545](https://tools.ietf.org/html/rfc5545) - Internet Calendaring and Scheduling Core Object Specification (iCalendar)
244+
- [RFC 4918](https://tools.ietf.org/html/rfc4918) - HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV)
245+
246+
## Contributing
247+
248+
Contributions to improve CalDAV support are welcome. Areas for enhancement include:
249+
250+
- Scheduling support (iTIP)
251+
- Additional client compatibility testing

examples/caldav.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//! CalDAV server example
2+
//!
3+
//! This example demonstrates how to set up a CalDAV server using the dav-server library.
4+
//! CalDAV is an extension of WebDAV for calendar data management.
5+
//!
6+
//! Usage:
7+
//! cargo run --example caldav --features caldav
8+
//!
9+
//! The server will be available at http://localhost:8080
10+
//! You can connect to it using CalDAV clients like Thunderbird, Apple Calendar, etc.
11+
12+
use dav_server::{DavHandler, DavMethodSet, fakels::FakeLs, memfs::MemFs};
13+
use hyper::{server::conn::http1, service::service_fn};
14+
use hyper_util::rt::TokioIo;
15+
use std::{convert::Infallible, net::SocketAddr};
16+
use tokio::net::TcpListener;
17+
18+
#[tokio::main]
19+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
20+
env_logger::init();
21+
22+
let addr: SocketAddr = ([127, 0, 0, 1], 8080).into();
23+
24+
// Set up the DAV handler with CalDAV support
25+
// Note: Using MemFs for this example because it supports the property operations
26+
// needed for CalDAV collections. For production use, you'd want a filesystem
27+
// implementation that persists properties to disk (e.g., using extended attributes
28+
// or sidecar files).
29+
let dav_server = DavHandler::builder()
30+
.filesystem(MemFs::new())
31+
.locksystem(FakeLs::new())
32+
.methods(DavMethodSet::all())
33+
.build_handler();
34+
35+
let listener = TcpListener::bind(addr).await?;
36+
37+
println!("CalDAV server listening on {}", addr);
38+
println!("Calendar collections can be accessed at http://{}", addr);
39+
println!();
40+
println!("NOTE: This example uses in-memory storage. Data will be lost when the server stops.");
41+
println!();
42+
println!("To create a calendar collection, use:");
43+
println!(" curl -i -X MKCALENDAR http://{}/my-calendar/", addr);
44+
println!();
45+
println!("To add a calendar event, use:");
46+
println!(" curl -i -X PUT http://{}/my-calendar/event1.ics \\", addr);
47+
println!(" -H 'Content-Type: text/calendar' \\");
48+
println!(" --data-binary @event.ics");
49+
println!();
50+
println!("Example event.ics content:");
51+
println!("BEGIN:VCALENDAR");
52+
println!("VERSION:2.0");
53+
println!("PRODID:-//Example Corp//CalDAV Client//EN");
54+
println!("BEGIN:VEVENT");
55+
println!("UID:[email protected]");
56+
println!("DTSTART:20240101T120000Z");
57+
println!("DTEND:20240101T130000Z");
58+
println!("SUMMARY:New Year Meeting");
59+
println!("DESCRIPTION:Planning meeting for the new year");
60+
println!("END:VEVENT");
61+
println!("END:VCALENDAR");
62+
63+
// Start the server loop
64+
loop {
65+
let (stream, _) = listener.accept().await?;
66+
let dav_server = dav_server.clone();
67+
68+
let io = TokioIo::new(stream);
69+
70+
tokio::task::spawn(async move {
71+
if let Err(err) = http1::Builder::new()
72+
.serve_connection(
73+
io,
74+
service_fn({
75+
move |req| {
76+
let dav_server = dav_server.clone();
77+
async move { Ok::<_, Infallible>(dav_server.handle(req).await) }
78+
}
79+
}),
80+
)
81+
.await
82+
{
83+
eprintln!("Failed serving connection: {err:?}");
84+
}
85+
});
86+
}
87+
}
88+
89+
#[cfg(not(feature = "caldav"))]
90+
fn main() {
91+
eprintln!("This example requires the 'caldav' feature to be enabled.");
92+
eprintln!("Run with: cargo run --example caldav --features caldav");
93+
std::process::exit(1);
94+
}

0 commit comments

Comments
 (0)