Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ jobs:
with:
command: build
args: --features actix-compat
- name: Check build with caldav
uses: actions-rs/cargo@v1
with:
command: build
args: --features caldav
- name: Test
uses: actions-rs/cargo@v1
with:
Expand Down
11 changes: 9 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "dav-server"
version = "0.8.0"
version = "0.9.0"
readme = "README.md"
description = "Rust WebDAV server library. A fork of the webdav-handler crate."
repository = "https://github.com/messense/dav-server-rs"
Expand All @@ -21,7 +21,8 @@ features = ["full"]
default = ["localfs", "memfs"]
actix-compat = [ "actix-web" ]
warp-compat = [ "warp", "hyper" ]
all = [ "actix-compat", "warp-compat" ]
caldav = [ "icalendar", "chrono" ]
all = [ "actix-compat", "warp-compat", "caldav" ]
localfs = ["libc", "lru", "parking_lot", "reflink-copy"]
memfs = ["libc"]

Expand All @@ -33,6 +34,10 @@ required-features = [ "actix-compat" ]
name = "warp"
required-features = [ "warp-compat" ]

[[example]]
name = "caldav"
required-features = [ "caldav" ]

[dependencies]
bytes = "1.0.1"
derivative = "2"
Expand Down Expand Up @@ -65,6 +70,8 @@ hyper = { version = "1.1.0", optional = true }
warp = { version = "0.3.0", optional = true, default-features = false }
actix-web = { version = "4.0.0-beta.15", optional = true }
reflink-copy = { version = "0.1.14", optional = true }
icalendar = { version = "0.17.1", optional = true }
chrono = { version = "0.4", optional = true }

[dev-dependencies]
clap = { version = "4.0.0", features = ["derive"] }
Expand Down
251 changes: 251 additions & 0 deletions README.CalDAV.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
# CalDAV Support in dav-server

This document describes the CalDAV (Calendaring Extensions to WebDAV) support in the dav-server library.

## Overview

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:

- Create and manage calendar collections
- Store and retrieve calendar events, tasks, and journals
- Query calendars with complex filters
- Synchronize calendar data between clients and servers

## Features

The CalDAV implementation in dav-server includes:

- **Calendar Collections**: Special WebDAV collections that contain calendar data
- **MKCALENDAR Method**: Create new calendar collections
- **REPORT Method**: Query calendar data with filters
- **CalDAV Properties**: Calendar-specific WebDAV properties
- **iCalendar Support**: Parse and validate iCalendar data
- **Time Range Queries**: Filter events by date/time ranges
- **Component Filtering**: Filter by calendar component types (VEVENT, VTODO, etc.)

## Enabling CalDAV

CalDAV support is available as an optional cargo feature:

```toml
[dependencies]
dav-server = { version = "0.8", features = ["caldav"] }
```

This adds the following dependencies:
- `icalendar`: For parsing and validating iCalendar data
- `chrono`: For date/time handling

## Quick Start

Here's a basic CalDAV server setup:

```rust
use dav_server::{DavHandler, fakels::FakeLs, localfs::LocalFs};

let server = DavHandler::builder()
.filesystem(LocalFs::new("/calendars", false, false, false))
.locksystem(FakeLs::new())
.build_handler();
```

## CalDAV Methods

### MKCALENDAR

Creates a new calendar collection:

```bash
curl -X MKCALENDAR http://localhost:8080/my-calendar/
```

With properties:

```bash
curl -X MKCALENDAR http://localhost:8080/my-calendar/ \
-H "Content-Type: application/xml" \
--data '<?xml version="1.0" encoding="utf-8" ?>
<C:mkcalendar xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:set>
<D:prop>
<D:displayname>My Calendar</D:displayname>
<C:calendar-description>Personal calendar</C:calendar-description>
</D:prop>
</D:set>
</C:mkcalendar>'
```

### REPORT

Query calendar data:

#### Calendar Query

```bash
curl -X REPORT http://localhost:8080/my-calendar/ \
-H "Content-Type: application/xml" \
-H "Depth: 1" \
--data '<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop>
<C:calendar-data/>
</D:prop>
<C:filter>
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
<C:time-range start="20240101T000000Z" end="20241231T235959Z"/>
</C:comp-filter>
</C:comp-filter>
</C:filter>
</C:calendar-query>'
```

#### Calendar Multiget

```bash
curl -X REPORT http://localhost:8080/my-calendar/ \
-H "Content-Type: application/xml" \
--data '<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-multiget xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop>
<C:calendar-data/>
</D:prop>
<D:href>/my-calendar/event1.ics</D:href>
<D:href>/my-calendar/event2.ics</D:href>
</C:calendar-multiget>'
```

## CalDAV Properties

The implementation supports standard CalDAV properties:

### Collection Properties

- `calendar-description`: Human-readable description
- `calendar-timezone`: Default timezone for the calendar
- `supported-calendar-component-set`: Supported component types (VEVENT, VTODO, etc.)
- `supported-calendar-data`: Supported calendar data formats
- `max-resource-size`: Maximum size for calendar resources

### Principal Properties

- `calendar-home-set`: URL of the user's calendar home collection
- `calendar-user-address-set`: Calendar user's addresses
- `schedule-inbox-URL`: URL for scheduling messages
- `schedule-outbox-URL`: URL for outgoing scheduling

## Working with Calendar Data

### Adding Events

Store iCalendar data using PUT:

```bash
curl -X PUT http://localhost:8080/my-calendar/event.ics \
-H "Content-Type: text/calendar" \
--data 'BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp//CalDAV Client//EN
BEGIN:VEVENT
UID:12345@example.com
DTSTART:20240101T120000Z
DTEND:20240101T130000Z
SUMMARY:New Year Meeting
DESCRIPTION:Planning meeting for the new year
END:VEVENT
END:VCALENDAR'
```

### Retrieving Events

Use GET to retrieve individual calendar resources:

```bash
curl http://localhost:8080/my-calendar/event.ics
```

## Client Compatibility

The CalDAV implementation has been tested with:

- **Thunderbird**: Full support for calendar sync
- **Apple Calendar**: Compatible with basic operations
- **CalDAV-Sync (Android)**: Works with standard CalDAV features
- **Evolution**: Support for calendar collections and events

## Limitations

Current limitations include:

- No scheduling support (iTIP/iMIP)
- Limited calendar-user-principal support
- No calendar sharing or ACL support
- Basic time zone handling
- No recurring event expansion in queries

## Example Applications
These calendar server examples lacks authentication and does not support user-specific access.
For a production environment, you should implement the GuardedFileSystem for better security and user management.

### Calendar Server

```rust
use dav_server::{DavHandler, fakels::FakeLs, localfs::LocalFs};
use std::net::SocketAddr;

#[tokio::main]
async fn main() {
let server = DavHandler::builder()
.filesystem(LocalFs::new("/calendars", false, false, false))
.locksystem(FakeLs::new())
// Use .strip_prefix if you want to start the handler with a prefix path like "/calendars".
// None will start with root ("/")
// .strip_prefix("/calendars")
.build_handler();

// Serve on port 8080
// Calendars accessible at http://localhost:8080/calendars/
}
```

### Multi-tenant Calendar Service

```rust
use dav_server::{DavHandler, memfs::MemFs, memls::MemLs};

// Use in-memory filesystem for demonstration
let server = DavHandler::builder()
.filesystem(MemFs::new())
.locksystem(MemLs::new())
.principal("/principals/user1/")
.build_handler();
```

## Testing

Run CalDAV tests with:

```bash
cargo test --features caldav caldav_tests
```

Run the CalDAV example:

```bash
cargo run --example caldav --features caldav
```

## Standards Compliance

This implementation follows:

- [RFC 4791](https://tools.ietf.org/html/rfc4791) - Calendaring Extensions to WebDAV (CalDAV)
- [RFC 5545](https://tools.ietf.org/html/rfc5545) - Internet Calendaring and Scheduling Core Object Specification (iCalendar)
- [RFC 4918](https://tools.ietf.org/html/rfc4918) - HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV)

## Contributing

Contributions to improve CalDAV support are welcome. Areas for enhancement include:

- Scheduling support (iTIP)
- Additional client compatibility testing
94 changes: 94 additions & 0 deletions examples/caldav.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//! CalDAV server example
//!
//! This example demonstrates how to set up a CalDAV server using the dav-server library.
//! CalDAV is an extension of WebDAV for calendar data management.
//!
//! Usage:
//! cargo run --example caldav --features caldav
//!
//! The server will be available at http://localhost:8080
//! You can connect to it using CalDAV clients like Thunderbird, Apple Calendar, etc.

use dav_server::{DavHandler, DavMethodSet, fakels::FakeLs, memfs::MemFs};
use hyper::{server::conn::http1, service::service_fn};
use hyper_util::rt::TokioIo;
use std::{convert::Infallible, net::SocketAddr};
use tokio::net::TcpListener;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();

let addr: SocketAddr = ([127, 0, 0, 1], 8080).into();

// Set up the DAV handler with CalDAV support
// Note: Using MemFs for this example because it supports the property operations
// needed for CalDAV collections. For production use, you'd want a filesystem
// implementation that persists properties to disk (e.g., using extended attributes
// or sidecar files).
let dav_server = DavHandler::builder()
.filesystem(MemFs::new())
.locksystem(FakeLs::new())
.methods(DavMethodSet::all())
.build_handler();

let listener = TcpListener::bind(addr).await?;

println!("CalDAV server listening on {}", addr);
println!("Calendar collections can be accessed at http://{}", addr);
println!();
println!("NOTE: This example uses in-memory storage. Data will be lost when the server stops.");
println!();
println!("To create a calendar collection, use:");
println!(" curl -i -X MKCALENDAR http://{}/my-calendar/", addr);
println!();
println!("To add a calendar event, use:");
println!(" curl -i -X PUT http://{}/my-calendar/event1.ics \\", addr);
println!(" -H 'Content-Type: text/calendar' \\");
println!(" --data-binary @event.ics");
println!();
println!("Example event.ics content:");
println!("BEGIN:VCALENDAR");
println!("VERSION:2.0");
println!("PRODID:-//Example Corp//CalDAV Client//EN");
println!("BEGIN:VEVENT");
println!("UID:12345@example.com");
println!("DTSTART:20240101T120000Z");
println!("DTEND:20240101T130000Z");
println!("SUMMARY:New Year Meeting");
println!("DESCRIPTION:Planning meeting for the new year");
println!("END:VEVENT");
println!("END:VCALENDAR");

// Start the server loop
loop {
let (stream, _) = listener.accept().await?;
let dav_server = dav_server.clone();

let io = TokioIo::new(stream);

tokio::task::spawn(async move {
if let Err(err) = http1::Builder::new()
.serve_connection(
io,
service_fn({
move |req| {
let dav_server = dav_server.clone();
async move { Ok::<_, Infallible>(dav_server.handle(req).await) }
}
}),
)
.await
{
eprintln!("Failed serving connection: {err:?}");
}
});
}
}

#[cfg(not(feature = "caldav"))]
fn main() {
eprintln!("This example requires the 'caldav' feature to be enabled.");
eprintln!("Run with: cargo run --example caldav --features caldav");
std::process::exit(1);
}
Loading
Loading