2024-01-04 10:51:14 +00:00
|
|
|
use anyhow::Context;
|
|
|
|
|
|
|
|
mod common;
|
2024-01-15 07:06:04 +00:00
|
|
|
use crate::common::constants::*;
|
2024-01-18 17:03:21 +00:00
|
|
|
use crate::common::fragments::*;
|
2024-01-04 10:51:14 +00:00
|
|
|
|
|
|
|
fn main() {
|
2024-05-22 17:36:27 +00:00
|
|
|
// IMAP
|
|
|
|
/*rfc3501_imap4rev1_base();
|
2024-01-04 10:51:14 +00:00
|
|
|
rfc6851_imapext_move();
|
2024-01-12 12:01:22 +00:00
|
|
|
rfc4551_imapext_condstore();
|
2024-01-19 13:13:43 +00:00
|
|
|
rfc2177_imapext_idle();
|
2024-05-22 17:36:27 +00:00
|
|
|
rfc5161_imapext_enable();
|
|
|
|
rfc3691_imapext_unselect();
|
|
|
|
rfc7888_imapext_literal();
|
|
|
|
rfc4315_imapext_uidplus();
|
|
|
|
rfc5819_imapext_liststatus();*/
|
|
|
|
|
|
|
|
// WebDAV
|
|
|
|
rfc4918_webdav_core();
|
2024-05-22 21:38:41 +00:00
|
|
|
rfc5397_webdav_principal();
|
2024-05-22 21:48:34 +00:00
|
|
|
rfc4791_webdav_caldav();
|
2024-05-28 14:03:25 +00:00
|
|
|
rfc6578_webdav_sync();
|
2024-01-12 12:01:22 +00:00
|
|
|
println!("✅ SUCCESS 🌟🚀🥳🙏🥹");
|
2024-01-04 10:51:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn rfc3501_imap4rev1_base() {
|
2024-01-12 12:01:22 +00:00
|
|
|
println!("🧪 rfc3501_imap4rev1_base");
|
2024-05-22 17:36:27 +00:00
|
|
|
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| {
|
2024-01-04 10:51:14 +00:00
|
|
|
connect(imap_socket).context("server says hello")?;
|
|
|
|
capability(imap_socket, Extension::None).context("check server capabilities")?;
|
|
|
|
login(imap_socket, Account::Alice).context("login test")?;
|
|
|
|
create_mailbox(imap_socket, Mailbox::Archive).context("created mailbox archive")?;
|
2024-01-18 17:03:21 +00:00
|
|
|
let select_res =
|
|
|
|
select(imap_socket, Mailbox::Inbox, SelectMod::None).context("select inbox")?;
|
2024-01-15 07:06:04 +00:00
|
|
|
assert!(select_res.contains("* 0 EXISTS"));
|
|
|
|
|
2024-01-04 10:51:14 +00:00
|
|
|
check(imap_socket).context("check must run")?;
|
2024-01-18 17:03:21 +00:00
|
|
|
status(imap_socket, Mailbox::Archive, StatusKind::UidNext)
|
|
|
|
.context("status of archive from inbox")?;
|
2024-01-04 10:51:14 +00:00
|
|
|
lmtp_handshake(lmtp_socket).context("handshake lmtp done")?;
|
|
|
|
lmtp_deliver_email(lmtp_socket, Email::Multipart).context("mail delivered successfully")?;
|
2024-01-12 12:01:22 +00:00
|
|
|
noop_exists(imap_socket, 1).context("noop loop must detect a new email")?;
|
2024-01-15 07:06:04 +00:00
|
|
|
|
2024-01-18 17:03:21 +00:00
|
|
|
let srv_msg = fetch(
|
|
|
|
imap_socket,
|
|
|
|
Selection::FirstId,
|
|
|
|
FetchKind::Rfc822,
|
|
|
|
FetchMod::None,
|
|
|
|
)
|
|
|
|
.context("fetch rfc822 message, should be our first message")?;
|
2024-01-15 07:06:04 +00:00
|
|
|
let orig_email = std::str::from_utf8(EMAIL1)?;
|
|
|
|
assert!(srv_msg.contains(orig_email));
|
2024-01-18 17:03:21 +00:00
|
|
|
|
2024-01-05 09:05:30 +00:00
|
|
|
copy(imap_socket, Selection::FirstId, Mailbox::Archive)
|
|
|
|
.context("copy message to the archive mailbox")?;
|
2024-01-20 10:45:32 +00:00
|
|
|
append(imap_socket, Email::Basic).context("insert email in INBOX")?;
|
2024-01-12 12:01:22 +00:00
|
|
|
noop_exists(imap_socket, 2).context("noop loop must detect a new email")?;
|
2024-01-15 07:06:04 +00:00
|
|
|
search(imap_socket, SearchKind::Text("OoOoO")).expect("search should return something");
|
2024-01-18 17:03:21 +00:00
|
|
|
store(
|
|
|
|
imap_socket,
|
|
|
|
Selection::FirstId,
|
|
|
|
Flag::Deleted,
|
|
|
|
StoreAction::AddFlags,
|
|
|
|
StoreMod::None,
|
|
|
|
)
|
|
|
|
.context("should add delete flag to the email")?;
|
2024-01-04 10:51:14 +00:00
|
|
|
expunge(imap_socket).context("expunge emails")?;
|
2024-01-05 09:05:30 +00:00
|
|
|
rename_mailbox(imap_socket, Mailbox::Archive, Mailbox::Drafts)
|
|
|
|
.context("Archive mailbox is renamed Drafts")?;
|
2024-01-04 10:51:14 +00:00
|
|
|
delete_mailbox(imap_socket, Mailbox::Drafts).context("Drafts mailbox is deleted")?;
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.expect("test fully run");
|
|
|
|
}
|
|
|
|
|
|
|
|
fn rfc3691_imapext_unselect() {
|
2024-01-12 12:01:22 +00:00
|
|
|
println!("🧪 rfc3691_imapext_unselect");
|
2024-05-22 17:36:27 +00:00
|
|
|
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| {
|
2024-01-04 10:51:14 +00:00
|
|
|
connect(imap_socket).context("server says hello")?;
|
|
|
|
|
|
|
|
lmtp_handshake(lmtp_socket).context("handshake lmtp done")?;
|
|
|
|
lmtp_deliver_email(lmtp_socket, Email::Basic).context("mail delivered successfully")?;
|
|
|
|
|
|
|
|
capability(imap_socket, Extension::Unselect).context("check server capabilities")?;
|
|
|
|
login(imap_socket, Account::Alice).context("login test")?;
|
2024-01-18 17:03:21 +00:00
|
|
|
let select_res =
|
|
|
|
select(imap_socket, Mailbox::Inbox, SelectMod::None).context("select inbox")?;
|
2024-01-15 07:06:04 +00:00
|
|
|
assert!(select_res.contains("* 0 EXISTS"));
|
|
|
|
|
2024-01-12 12:01:22 +00:00
|
|
|
noop_exists(imap_socket, 1).context("noop loop must detect a new email")?;
|
2024-01-18 17:03:21 +00:00
|
|
|
store(
|
|
|
|
imap_socket,
|
|
|
|
Selection::FirstId,
|
|
|
|
Flag::Deleted,
|
|
|
|
StoreAction::AddFlags,
|
|
|
|
StoreMod::None,
|
|
|
|
)
|
|
|
|
.context("add delete flags to the email")?;
|
2024-01-04 10:51:14 +00:00
|
|
|
unselect(imap_socket)
|
|
|
|
.context("unselect inbox while preserving email with the \\Delete flag")?;
|
2024-01-18 17:03:21 +00:00
|
|
|
let select_res =
|
|
|
|
select(imap_socket, Mailbox::Inbox, SelectMod::None).context("select inbox again")?;
|
2024-01-15 07:06:04 +00:00
|
|
|
assert!(select_res.contains("* 1 EXISTS"));
|
|
|
|
|
2024-01-18 17:03:21 +00:00
|
|
|
let srv_msg = fetch(
|
|
|
|
imap_socket,
|
|
|
|
Selection::FirstId,
|
|
|
|
FetchKind::Rfc822,
|
|
|
|
FetchMod::None,
|
|
|
|
)
|
|
|
|
.context("message is still present")?;
|
2024-01-15 07:06:04 +00:00
|
|
|
let orig_email = std::str::from_utf8(EMAIL2)?;
|
|
|
|
assert!(srv_msg.contains(orig_email));
|
|
|
|
|
2024-01-04 10:51:14 +00:00
|
|
|
close(imap_socket).context("close inbox and expunge message")?;
|
2024-01-15 07:06:04 +00:00
|
|
|
let select_res = select(imap_socket, Mailbox::Inbox, SelectMod::None)
|
2024-01-05 09:05:30 +00:00
|
|
|
.context("select inbox again and check it's empty")?;
|
2024-01-15 07:06:04 +00:00
|
|
|
assert!(select_res.contains("* 0 EXISTS"));
|
2024-01-04 10:51:14 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.expect("test fully run");
|
|
|
|
}
|
|
|
|
|
|
|
|
fn rfc5161_imapext_enable() {
|
2024-01-12 12:01:22 +00:00
|
|
|
println!("🧪 rfc5161_imapext_enable");
|
2024-05-22 17:36:27 +00:00
|
|
|
common::aerogramme_provider_daemon_dev(|imap_socket, _lmtp_socket, _dav_socket| {
|
2024-01-04 10:51:14 +00:00
|
|
|
connect(imap_socket).context("server says hello")?;
|
|
|
|
login(imap_socket, Account::Alice).context("login test")?;
|
|
|
|
enable(imap_socket, Enable::Utf8Accept, Some(Enable::Utf8Accept))?;
|
|
|
|
enable(imap_socket, Enable::Utf8Accept, None)?;
|
|
|
|
logout(imap_socket)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.expect("test fully run");
|
|
|
|
}
|
|
|
|
|
|
|
|
fn rfc6851_imapext_move() {
|
2024-01-12 12:01:22 +00:00
|
|
|
println!("🧪 rfc6851_imapext_move");
|
2024-05-22 17:36:27 +00:00
|
|
|
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| {
|
2024-01-04 10:51:14 +00:00
|
|
|
connect(imap_socket).context("server says hello")?;
|
|
|
|
|
|
|
|
capability(imap_socket, Extension::Move).context("check server capabilities")?;
|
|
|
|
login(imap_socket, Account::Alice).context("login test")?;
|
|
|
|
create_mailbox(imap_socket, Mailbox::Archive).context("created mailbox archive")?;
|
2024-01-18 17:03:21 +00:00
|
|
|
let select_res =
|
|
|
|
select(imap_socket, Mailbox::Inbox, SelectMod::None).context("select inbox")?;
|
2024-01-15 07:06:04 +00:00
|
|
|
assert!(select_res.contains("* 0 EXISTS"));
|
2024-01-04 10:51:14 +00:00
|
|
|
|
|
|
|
lmtp_handshake(lmtp_socket).context("handshake lmtp done")?;
|
|
|
|
lmtp_deliver_email(lmtp_socket, Email::Basic).context("mail delivered successfully")?;
|
|
|
|
|
2024-01-12 12:01:22 +00:00
|
|
|
noop_exists(imap_socket, 1).context("noop loop must detect a new email")?;
|
2024-01-05 09:05:30 +00:00
|
|
|
r#move(imap_socket, Selection::FirstId, Mailbox::Archive)
|
|
|
|
.context("message from inbox moved to archive")?;
|
2024-01-04 10:51:14 +00:00
|
|
|
|
|
|
|
unselect(imap_socket)
|
|
|
|
.context("unselect inbox while preserving email with the \\Delete flag")?;
|
2024-01-18 17:03:21 +00:00
|
|
|
let select_res =
|
|
|
|
select(imap_socket, Mailbox::Archive, SelectMod::None).context("select archive")?;
|
2024-01-15 07:06:04 +00:00
|
|
|
assert!(select_res.contains("* 1 EXISTS"));
|
|
|
|
|
|
|
|
let srv_msg = fetch(
|
2024-01-18 17:03:21 +00:00
|
|
|
imap_socket,
|
|
|
|
Selection::FirstId,
|
|
|
|
FetchKind::Rfc822,
|
2024-01-15 07:06:04 +00:00
|
|
|
FetchMod::None,
|
2024-01-18 17:03:21 +00:00
|
|
|
)
|
|
|
|
.context("check mail exists")?;
|
2024-01-15 07:06:04 +00:00
|
|
|
let orig_email = std::str::from_utf8(EMAIL2)?;
|
|
|
|
assert!(srv_msg.contains(orig_email));
|
|
|
|
|
2024-01-04 10:51:14 +00:00
|
|
|
logout(imap_socket).context("must quit")?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.expect("test fully run");
|
|
|
|
}
|
2024-01-04 11:09:16 +00:00
|
|
|
|
|
|
|
fn rfc7888_imapext_literal() {
|
2024-01-12 12:01:22 +00:00
|
|
|
println!("🧪 rfc7888_imapext_literal");
|
2024-05-22 17:36:27 +00:00
|
|
|
common::aerogramme_provider_daemon_dev(|imap_socket, _lmtp_socket, _dav_socket| {
|
2024-01-04 11:09:16 +00:00
|
|
|
connect(imap_socket).context("server says hello")?;
|
|
|
|
|
|
|
|
capability(imap_socket, Extension::LiteralPlus).context("check server capabilities")?;
|
|
|
|
login_with_literal(imap_socket, Account::Alice).context("use literal to connect Alice")?;
|
|
|
|
|
|
|
|
Ok(())
|
2024-01-05 09:05:30 +00:00
|
|
|
})
|
|
|
|
.expect("test fully run");
|
2024-01-04 11:09:16 +00:00
|
|
|
}
|
2024-01-12 12:01:22 +00:00
|
|
|
|
|
|
|
fn rfc4551_imapext_condstore() {
|
|
|
|
println!("🧪 rfc4551_imapext_condstore");
|
2024-05-22 17:36:27 +00:00
|
|
|
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| {
|
2024-01-12 14:02:02 +00:00
|
|
|
// Setup the test
|
2024-01-12 12:01:22 +00:00
|
|
|
connect(imap_socket).context("server says hello")?;
|
2024-01-15 07:06:04 +00:00
|
|
|
|
|
|
|
// RFC 3.1.1 Advertising Support for CONDSTORE
|
2024-01-12 12:01:22 +00:00
|
|
|
capability(imap_socket, Extension::Condstore).context("check server capabilities")?;
|
|
|
|
login(imap_socket, Account::Alice).context("login test")?;
|
2024-01-12 14:02:02 +00:00
|
|
|
|
2024-01-15 07:06:04 +00:00
|
|
|
// RFC 3.1.8. CONDSTORE Parameter to SELECT and EXAMINE
|
2024-01-18 17:03:21 +00:00
|
|
|
let select_res =
|
|
|
|
select(imap_socket, Mailbox::Inbox, SelectMod::Condstore).context("select inbox")?;
|
2024-01-15 07:06:04 +00:00
|
|
|
// RFC 3.1.2 New OK Untagged Responses for SELECT and EXAMINE
|
|
|
|
assert!(select_res.contains("[HIGHESTMODSEQ 1]"));
|
2024-01-12 14:02:02 +00:00
|
|
|
|
2024-01-15 07:06:04 +00:00
|
|
|
// RFC 3.1.3. STORE and UID STORE Commands
|
2024-01-12 14:02:02 +00:00
|
|
|
lmtp_handshake(lmtp_socket).context("handshake lmtp done")?;
|
|
|
|
lmtp_deliver_email(lmtp_socket, Email::Basic).context("mail delivered successfully")?;
|
|
|
|
lmtp_deliver_email(lmtp_socket, Email::Multipart).context("mail delivered successfully")?;
|
2024-01-12 12:01:22 +00:00
|
|
|
noop_exists(imap_socket, 2).context("noop loop must detect a new email")?;
|
2024-01-18 17:03:21 +00:00
|
|
|
let store_res = store(
|
|
|
|
imap_socket,
|
|
|
|
Selection::All,
|
|
|
|
Flag::Important,
|
|
|
|
StoreAction::AddFlags,
|
|
|
|
StoreMod::UnchangedSince(1),
|
|
|
|
)?;
|
2024-01-15 07:06:04 +00:00
|
|
|
assert!(store_res.contains("[MODIFIED 2]"));
|
|
|
|
assert!(store_res.contains("* 1 FETCH (FLAGS (\\Important) MODSEQ (3))"));
|
|
|
|
assert!(!store_res.contains("* 2 FETCH"));
|
|
|
|
assert_eq!(store_res.lines().count(), 2);
|
|
|
|
|
|
|
|
// RFC 3.1.4. FETCH and UID FETCH Commands
|
2024-01-18 17:03:21 +00:00
|
|
|
let fetch_res = fetch(
|
|
|
|
imap_socket,
|
|
|
|
Selection::All,
|
|
|
|
FetchKind::Rfc822Size,
|
|
|
|
FetchMod::ChangedSince(2),
|
|
|
|
)?;
|
2024-02-20 10:42:51 +00:00
|
|
|
assert!(fetch_res.contains("* 1 FETCH (RFC822.SIZE 81 MODSEQ (3))"));
|
2024-01-15 07:06:04 +00:00
|
|
|
assert!(!fetch_res.contains("* 2 FETCH"));
|
|
|
|
assert_eq!(store_res.lines().count(), 2);
|
|
|
|
|
|
|
|
// RFC 3.1.5. MODSEQ Search Criterion in SEARCH
|
|
|
|
let search_res = search(imap_socket, SearchKind::ModSeq(3))?;
|
|
|
|
// RFC 3.1.6. Modified SEARCH Untagged Response
|
|
|
|
assert!(search_res.contains("* SEARCH 1 (MODSEQ 3)"));
|
|
|
|
|
|
|
|
// RFC 3.1.7 HIGHESTMODSEQ Status Data Items
|
|
|
|
let status_res = status(imap_socket, Mailbox::Inbox, StatusKind::HighestModSeq)?;
|
|
|
|
assert!(status_res.contains("HIGHESTMODSEQ 3"));
|
2024-01-12 14:02:02 +00:00
|
|
|
|
2024-01-12 12:01:22 +00:00
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.expect("test fully run");
|
|
|
|
}
|
2024-01-19 13:13:43 +00:00
|
|
|
|
|
|
|
fn rfc2177_imapext_idle() {
|
|
|
|
println!("🧪 rfc2177_imapext_idle");
|
2024-05-22 17:36:27 +00:00
|
|
|
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| {
|
2024-01-20 10:45:32 +00:00
|
|
|
// Test setup, check capability
|
2024-01-19 13:13:43 +00:00
|
|
|
connect(imap_socket).context("server says hello")?;
|
|
|
|
capability(imap_socket, Extension::Idle).context("check server capabilities")?;
|
|
|
|
login(imap_socket, Account::Alice).context("login test")?;
|
|
|
|
select(imap_socket, Mailbox::Inbox, SelectMod::None).context("select inbox")?;
|
|
|
|
|
|
|
|
// Check that new messages from LMTP are correctly detected during idling
|
|
|
|
start_idle(imap_socket).context("can't start idling")?;
|
|
|
|
lmtp_handshake(lmtp_socket).context("handshake lmtp done")?;
|
|
|
|
lmtp_deliver_email(lmtp_socket, Email::Basic).context("mail delivered successfully")?;
|
|
|
|
let srv_msg = stop_idle(imap_socket).context("stop idling")?;
|
|
|
|
assert!(srv_msg.contains("* 1 EXISTS"));
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.expect("test fully run");
|
|
|
|
}
|
2024-01-20 10:45:32 +00:00
|
|
|
|
|
|
|
fn rfc4315_imapext_uidplus() {
|
|
|
|
println!("🧪 rfc4315_imapext_uidplus");
|
2024-05-22 17:36:27 +00:00
|
|
|
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| {
|
2024-01-20 10:45:32 +00:00
|
|
|
// Test setup, check capability, insert 2 emails
|
|
|
|
connect(imap_socket).context("server says hello")?;
|
|
|
|
capability(imap_socket, Extension::UidPlus).context("check server capabilities")?;
|
|
|
|
login(imap_socket, Account::Alice).context("login test")?;
|
|
|
|
select(imap_socket, Mailbox::Inbox, SelectMod::None).context("select inbox")?;
|
|
|
|
lmtp_handshake(lmtp_socket).context("handshake lmtp done")?;
|
|
|
|
lmtp_deliver_email(lmtp_socket, Email::Basic).context("mail delivered successfully")?;
|
|
|
|
lmtp_deliver_email(lmtp_socket, Email::Multipart).context("mail delivered successfully")?;
|
|
|
|
noop_exists(imap_socket, 2).context("noop loop must detect a new email")?;
|
|
|
|
|
|
|
|
// Check UID EXPUNGE seqset
|
|
|
|
store(
|
|
|
|
imap_socket,
|
|
|
|
Selection::All,
|
|
|
|
Flag::Deleted,
|
|
|
|
StoreAction::AddFlags,
|
|
|
|
StoreMod::None,
|
|
|
|
)?;
|
|
|
|
let res = uid_expunge(imap_socket, Selection::FirstId)?;
|
|
|
|
assert_eq!(res.lines().count(), 2);
|
|
|
|
assert!(res.contains("* 1 EXPUNGE"));
|
|
|
|
|
|
|
|
// APPENDUID check UID + UID VALIDITY
|
|
|
|
// Note: 4 and not 3, as we update the UID counter when we delete an email
|
|
|
|
// it's part of our UID proof
|
|
|
|
let res = append(imap_socket, Email::Multipart)?;
|
|
|
|
assert!(res.contains("[APPENDUID 1 4]"));
|
|
|
|
|
|
|
|
// COPYUID, check
|
|
|
|
create_mailbox(imap_socket, Mailbox::Archive).context("created mailbox archive")?;
|
|
|
|
let res = copy(imap_socket, Selection::FirstId, Mailbox::Archive)?;
|
|
|
|
assert!(res.contains("[COPYUID 1 2 1]"));
|
|
|
|
|
|
|
|
// MOVEUID, check
|
|
|
|
let res = r#move(imap_socket, Selection::FirstId, Mailbox::Archive)?;
|
|
|
|
assert!(res.contains("[COPYUID 1 2 2]"));
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.expect("test fully run");
|
|
|
|
}
|
2024-01-20 18:23:44 +00:00
|
|
|
|
|
|
|
///
|
|
|
|
/// Example
|
|
|
|
///
|
|
|
|
/// ```text
|
|
|
|
/// 30 list "" "*" RETURN (STATUS (MESSAGES UNSEEN))
|
|
|
|
/// * LIST (\Subscribed) "." INBOX
|
|
|
|
/// * STATUS INBOX (MESSAGES 2 UNSEEN 1)
|
|
|
|
/// 30 OK LIST completed
|
|
|
|
/// ```
|
|
|
|
fn rfc5819_imapext_liststatus() {
|
|
|
|
println!("🧪 rfc5819_imapext_liststatus");
|
2024-05-22 17:36:27 +00:00
|
|
|
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| {
|
2024-01-20 18:23:44 +00:00
|
|
|
// Test setup, check capability, add 2 emails, read 1
|
|
|
|
connect(imap_socket).context("server says hello")?;
|
|
|
|
capability(imap_socket, Extension::ListStatus).context("check server capabilities")?;
|
|
|
|
login(imap_socket, Account::Alice).context("login test")?;
|
|
|
|
select(imap_socket, Mailbox::Inbox, SelectMod::None).context("select inbox")?;
|
|
|
|
lmtp_handshake(lmtp_socket).context("handshake lmtp done")?;
|
|
|
|
lmtp_deliver_email(lmtp_socket, Email::Basic).context("mail delivered successfully")?;
|
|
|
|
lmtp_deliver_email(lmtp_socket, Email::Multipart).context("mail delivered successfully")?;
|
|
|
|
noop_exists(imap_socket, 2).context("noop loop must detect a new email")?;
|
|
|
|
fetch(
|
|
|
|
imap_socket,
|
|
|
|
Selection::FirstId,
|
|
|
|
FetchKind::Rfc822,
|
|
|
|
FetchMod::None,
|
|
|
|
)
|
|
|
|
.context("read one message")?;
|
|
|
|
close(imap_socket).context("close inbox")?;
|
|
|
|
|
|
|
|
// Test return status MESSAGES UNSEEN
|
|
|
|
let ret = list(
|
|
|
|
imap_socket,
|
|
|
|
MbxSelect::All,
|
|
|
|
ListReturn::StatusMessagesUnseen,
|
|
|
|
)?;
|
|
|
|
assert!(ret.contains("* STATUS INBOX (MESSAGES 2 UNSEEN 1)"));
|
|
|
|
|
|
|
|
// Test that without RETURN, no status is sent
|
|
|
|
let ret = list(imap_socket, MbxSelect::All, ListReturn::None)?;
|
|
|
|
assert!(!ret.contains("* STATUS"));
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.expect("test fully run");
|
|
|
|
}
|
2024-05-22 17:36:27 +00:00
|
|
|
|
2024-05-22 21:38:41 +00:00
|
|
|
use aero_dav::acltypes as acl;
|
2024-05-22 17:36:27 +00:00
|
|
|
use aero_dav::caltypes as cal;
|
2024-05-22 21:38:41 +00:00
|
|
|
use aero_dav::realization::{self, All};
|
2024-05-28 14:03:25 +00:00
|
|
|
use aero_dav::synctypes as sync;
|
2024-05-22 17:36:27 +00:00
|
|
|
use aero_dav::types as dav;
|
2024-05-28 14:03:25 +00:00
|
|
|
use aero_dav::versioningtypes as vers;
|
2024-05-22 17:36:27 +00:00
|
|
|
|
|
|
|
use crate::common::dav_deserialize;
|
|
|
|
|
|
|
|
fn rfc4918_webdav_core() {
|
|
|
|
println!("🧪 rfc4918_webdav_core");
|
|
|
|
common::aerogramme_provider_daemon_dev(|_imap, _lmtp, http| {
|
|
|
|
// --- PROPFIND ---
|
|
|
|
// empty request body (assume "allprop")
|
|
|
|
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087").send()?.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
let root_propstats = multistatus.responses.iter()
|
|
|
|
.find_map(|v| match &v.status_or_propstat {
|
|
|
|
dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/" => Some(x),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.expect("propstats for root must exist");
|
2024-05-22 21:38:41 +00:00
|
|
|
|
2024-05-22 17:36:27 +00:00
|
|
|
let root_success = root_propstats.iter().find(|p| p.status.0.as_u16() == 200).expect("some propstats for root must be 200");
|
|
|
|
let display_name = root_success.prop.0.iter()
|
|
|
|
.find_map(|v| match v { dav::AnyProperty::Value(dav::Property::DisplayName(x)) => Some(x), _ => None } )
|
|
|
|
.expect("root has a display name");
|
|
|
|
let content_type = root_success.prop.0.iter()
|
|
|
|
.find_map(|v| match v { dav::AnyProperty::Value(dav::Property::GetContentType(x)) => Some(x), _ => None } )
|
|
|
|
.expect("root has a content type");
|
|
|
|
let resource_type = root_success.prop.0.iter()
|
|
|
|
.find_map(|v| match v { dav::AnyProperty::Value(dav::Property::ResourceType(x)) => Some(x), _ => None } )
|
|
|
|
.expect("root has a resource type");
|
|
|
|
|
|
|
|
assert_eq!(display_name, "DAV Root");
|
|
|
|
assert_eq!(content_type, "httpd/unix-directory");
|
|
|
|
assert_eq!(resource_type, &[ dav::ResourceType::Collection ]);
|
|
|
|
|
|
|
|
// propname
|
|
|
|
let propfind_req = r#"<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><propname/></propfind>"#;
|
|
|
|
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087").body(propfind_req).send()?.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
let root_propstats = multistatus.responses.iter()
|
|
|
|
.find_map(|v| match &v.status_or_propstat {
|
|
|
|
dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/" => Some(x),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.expect("propstats for root must exist");
|
|
|
|
let root_success = root_propstats.iter().find(|p| p.status.0.as_u16() == 200).expect("some propstats for root must be 200");
|
|
|
|
assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Request(dav::PropertyRequest::DisplayName))).is_some());
|
|
|
|
assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Request(dav::PropertyRequest::ResourceType))).is_some());
|
|
|
|
assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Request(dav::PropertyRequest::GetContentType))).is_some());
|
|
|
|
|
|
|
|
// list of properties
|
|
|
|
let propfind_req = r#"<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><prop><displayname/><getcontentlength/></prop></propfind>"#;
|
|
|
|
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087").body(propfind_req).send()?.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
let root_propstats = multistatus.responses.iter()
|
|
|
|
.find_map(|v| match &v.status_or_propstat {
|
|
|
|
dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/" => Some(x),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.expect("propstats for root must exist");
|
|
|
|
|
|
|
|
let root_success = root_propstats.iter().find(|p| p.status.0.as_u16() == 200).expect("some propstats for root must be 200");
|
|
|
|
let root_not_found = root_propstats.iter().find(|p| p.status.0.as_u16() == 404).expect("some propstats for root must be not found");
|
|
|
|
|
|
|
|
assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Value(dav::Property::DisplayName(x)) if x == "DAV Root")).is_some());
|
|
|
|
assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Value(dav::Property::ResourceType(_)))).is_none());
|
|
|
|
assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Value(dav::Property::GetContentType(_)))).is_none());
|
|
|
|
assert!(root_not_found.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Request(dav::PropertyRequest::GetContentLength))).is_some());
|
|
|
|
|
2024-05-22 17:58:20 +00:00
|
|
|
// depth 1 / -> /alice/
|
|
|
|
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087").header("Depth", "1").send()?.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
let _user_propstats = multistatus.responses.iter()
|
|
|
|
.find_map(|v| match &v.status_or_propstat {
|
|
|
|
dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/alice/" => Some(x),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.expect("user collection must exist");
|
|
|
|
|
|
|
|
// depth 1 /alice/ -> /alice/calendar/
|
|
|
|
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087/alice/").header("Depth", "1").send()?.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
let _user_calendars_propstats = multistatus.responses.iter()
|
|
|
|
.find_map(|v| match &v.status_or_propstat {
|
|
|
|
dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/alice/calendar/" => Some(x),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.expect("user collection must exist");
|
|
|
|
|
|
|
|
// depth 1 /alice/calendar/ -> /alice/calendar/Personal/
|
|
|
|
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087/alice/calendar/").header("Depth", "1").send()?.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
let _user_calendars_propstats = multistatus.responses.iter()
|
|
|
|
.find_map(|v| match &v.status_or_propstat {
|
|
|
|
dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/alice/calendar/Personal/" => Some(x),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.expect("Personal calendar must exist");
|
2024-05-22 17:36:27 +00:00
|
|
|
|
2024-05-22 17:58:20 +00:00
|
|
|
// depth 1 /alice/calendar/Personal/ -> empty for now...
|
|
|
|
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087/alice/calendar/Personal/").header("Depth", "1").send()?.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
assert_eq!(multistatus.responses.len(), 1);
|
2024-05-22 17:36:27 +00:00
|
|
|
|
|
|
|
// --- PUT ---
|
2024-05-22 21:22:03 +00:00
|
|
|
// first object
|
|
|
|
let resp = http.put("http://localhost:8087/alice/calendar/Personal/rfc2.ics").header("If-None-Match", "*").body(ICAL_RFC2).send()?;
|
|
|
|
let obj1_etag = resp.headers().get("etag").expect("etag must be set");
|
2024-05-22 17:58:20 +00:00
|
|
|
assert_eq!(resp.status(), 201);
|
|
|
|
|
|
|
|
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087/alice/calendar/Personal/").header("Depth", "1").send()?.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
assert_eq!(multistatus.responses.len(), 2);
|
|
|
|
|
2024-05-22 21:22:03 +00:00
|
|
|
// second object
|
|
|
|
let resp = http.put("http://localhost:8087/alice/calendar/Personal/rfc3.ics").header("If-None-Match", "*").body(ICAL_RFC3).send()?;
|
2024-05-22 17:58:20 +00:00
|
|
|
assert_eq!(resp.status(), 201);
|
|
|
|
|
|
|
|
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087/alice/calendar/Personal/").header("Depth", "1").send()?.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
assert_eq!(multistatus.responses.len(), 3);
|
2024-05-22 17:36:27 +00:00
|
|
|
|
2024-05-22 21:22:03 +00:00
|
|
|
// can't create an event on an existing path
|
|
|
|
let resp = http.put("http://localhost:8087/alice/calendar/Personal/rfc2.ics").header("If-None-Match", "*").body(ICAL_RFC1).send()?;
|
|
|
|
assert_eq!(resp.status(), 412);
|
|
|
|
|
|
|
|
// update first object by knowing its ETag
|
|
|
|
let resp = http.put("http://localhost:8087/alice/calendar/Personal/rfc2.ics").header("If-Match", obj1_etag).body(ICAL_RFC1).send()?;
|
|
|
|
assert_eq!(resp.status(), 201);
|
|
|
|
|
2024-05-22 17:36:27 +00:00
|
|
|
// --- GET ---
|
2024-05-22 21:22:03 +00:00
|
|
|
let body = http.get("http://localhost:8087/alice/calendar/Personal/rfc2.ics").send()?.text()?;
|
|
|
|
assert_eq!(body.as_bytes(), ICAL_RFC1);
|
|
|
|
|
|
|
|
let body = http.get("http://localhost:8087/alice/calendar/Personal/rfc3.ics").send()?.text()?;
|
|
|
|
assert_eq!(body.as_bytes(), ICAL_RFC3);
|
2024-05-22 21:38:41 +00:00
|
|
|
|
2024-05-22 17:36:27 +00:00
|
|
|
// --- DELETE ---
|
2024-05-22 21:22:03 +00:00
|
|
|
// delete 1st object
|
|
|
|
let resp = http.delete("http://localhost:8087/alice/calendar/Personal/rfc2.ics").send()?;
|
|
|
|
assert_eq!(resp.status(), 204);
|
|
|
|
|
|
|
|
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087/alice/calendar/Personal/").header("Depth", "1").send()?.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
assert_eq!(multistatus.responses.len(), 2);
|
|
|
|
|
|
|
|
// delete 2nd object
|
|
|
|
let resp = http.delete("http://localhost:8087/alice/calendar/Personal/rfc3.ics").send()?;
|
|
|
|
assert_eq!(resp.status(), 204);
|
2024-05-22 17:36:27 +00:00
|
|
|
|
2024-05-22 21:22:03 +00:00
|
|
|
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087/alice/calendar/Personal/").header("Depth", "1").send()?.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
assert_eq!(multistatus.responses.len(), 1);
|
2024-05-22 17:36:27 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.expect("test fully run");
|
|
|
|
}
|
|
|
|
|
2024-05-22 21:38:41 +00:00
|
|
|
fn rfc5397_webdav_principal() {
|
|
|
|
println!("🧪 rfc5397_webdav_principal");
|
|
|
|
common::aerogramme_provider_daemon_dev(|_imap, _lmtp, http| {
|
|
|
|
// Find principal
|
|
|
|
let propfind_req = r#"<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><prop><current-user-principal/></prop></propfind>"#;
|
|
|
|
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087").body(propfind_req).send()?.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
let root_propstats = multistatus.responses.iter()
|
|
|
|
.find_map(|v| match &v.status_or_propstat {
|
|
|
|
dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/" => Some(x),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.expect("propstats for root must exist");
|
|
|
|
|
|
|
|
let root_success = root_propstats.iter().find(|p| p.status.0.as_u16() == 200).expect("current-user-principal must exist");
|
|
|
|
let principal = root_success.prop.0.iter()
|
|
|
|
.find_map(|v| match v {
|
|
|
|
dav::AnyProperty::Value(dav::Property::Extension(realization::Property::Acl(acl::Property::CurrentUserPrincipal(acl::User::Authenticated(dav::Href(x)))))) => Some(x),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.expect("request returned an authenticated principal");
|
|
|
|
assert_eq!(principal, "/alice/");
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.expect("test fully run")
|
|
|
|
}
|
2024-05-22 17:36:27 +00:00
|
|
|
|
2024-05-22 21:48:34 +00:00
|
|
|
fn rfc4791_webdav_caldav() {
|
|
|
|
println!("🧪 rfc4791_webdav_caldav");
|
|
|
|
common::aerogramme_provider_daemon_dev(|_imap, _lmtp, http| {
|
2024-05-23 06:55:53 +00:00
|
|
|
// --- INITIAL TEST SETUP ---
|
2024-05-26 09:03:39 +00:00
|
|
|
// Add entries
|
2024-05-23 06:55:53 +00:00
|
|
|
let resp = http
|
|
|
|
.put("http://localhost:8087/alice/calendar/Personal/rfc1.ics")
|
|
|
|
.header("If-None-Match", "*")
|
|
|
|
.body(ICAL_RFC1)
|
|
|
|
.send()?;
|
|
|
|
let obj1_etag = resp.headers().get("etag").expect("etag must be set");
|
|
|
|
assert_eq!(resp.status(), 201);
|
|
|
|
let resp = http
|
|
|
|
.put("http://localhost:8087/alice/calendar/Personal/rfc2.ics")
|
|
|
|
.header("If-None-Match", "*")
|
|
|
|
.body(ICAL_RFC2)
|
|
|
|
.send()?;
|
|
|
|
let obj2_etag = resp.headers().get("etag").expect("etag must be set");
|
|
|
|
assert_eq!(resp.status(), 201);
|
|
|
|
let resp = http
|
|
|
|
.put("http://localhost:8087/alice/calendar/Personal/rfc3.ics")
|
|
|
|
.header("If-None-Match", "*")
|
|
|
|
.body(ICAL_RFC3)
|
|
|
|
.send()?;
|
|
|
|
let obj3_etag = resp.headers().get("etag").expect("etag must be set");
|
|
|
|
assert_eq!(resp.status(), 201);
|
|
|
|
let resp = http
|
|
|
|
.put("http://localhost:8087/alice/calendar/Personal/rfc4.ics")
|
|
|
|
.header("If-None-Match", "*")
|
|
|
|
.body(ICAL_RFC4)
|
|
|
|
.send()?;
|
2024-05-23 07:24:06 +00:00
|
|
|
let _obj4_etag = resp.headers().get("etag").expect("etag must be set");
|
2024-05-23 06:55:53 +00:00
|
|
|
assert_eq!(resp.status(), 201);
|
|
|
|
let resp = http
|
|
|
|
.put("http://localhost:8087/alice/calendar/Personal/rfc5.ics")
|
|
|
|
.header("If-None-Match", "*")
|
|
|
|
.body(ICAL_RFC5)
|
|
|
|
.send()?;
|
2024-05-23 07:24:06 +00:00
|
|
|
let _obj5_etag = resp.headers().get("etag").expect("etag must be set");
|
2024-05-23 06:55:53 +00:00
|
|
|
assert_eq!(resp.status(), 201);
|
2024-05-26 08:33:04 +00:00
|
|
|
let resp = http
|
|
|
|
.put("http://localhost:8087/alice/calendar/Personal/rfc6.ics")
|
|
|
|
.header("If-None-Match", "*")
|
|
|
|
.body(ICAL_RFC6)
|
|
|
|
.send()?;
|
2024-05-26 09:03:39 +00:00
|
|
|
let obj6_etag = resp.headers().get("etag").expect("etag must be set");
|
|
|
|
assert_eq!(resp.status(), 201);
|
|
|
|
let resp = http
|
|
|
|
.put("http://localhost:8087/alice/calendar/Personal/rfc7.ics")
|
|
|
|
.header("If-None-Match", "*")
|
|
|
|
.body(ICAL_RFC7)
|
|
|
|
.send()?;
|
|
|
|
let obj7_etag = resp.headers().get("etag").expect("etag must be set");
|
2024-05-26 08:33:04 +00:00
|
|
|
assert_eq!(resp.status(), 201);
|
2024-05-23 06:55:53 +00:00
|
|
|
|
2024-05-23 07:24:06 +00:00
|
|
|
// A generic function to check a <calendar-data/> query result
|
2024-05-23 08:01:43 +00:00
|
|
|
let check_cal =
|
2024-05-23 07:24:06 +00:00
|
|
|
|multistatus: &dav::Multistatus<All>,
|
2024-05-23 08:01:43 +00:00
|
|
|
(ref_path, ref_etag, ref_ical): (&str, Option<&str>, Option<&[u8]>)| {
|
2024-05-23 07:24:06 +00:00
|
|
|
let obj_stats = multistatus
|
|
|
|
.responses
|
|
|
|
.iter()
|
|
|
|
.find_map(|v| match &v.status_or_propstat {
|
|
|
|
dav::StatusOrPropstat::PropStat(dav::Href(p), x)
|
|
|
|
if p.as_str() == ref_path =>
|
|
|
|
{
|
|
|
|
Some(x)
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.expect("propstats must exist");
|
|
|
|
let obj_success = obj_stats
|
|
|
|
.iter()
|
|
|
|
.find(|p| p.status.0.as_u16() == 200)
|
|
|
|
.expect("some propstats must be 200");
|
2024-05-26 08:33:04 +00:00
|
|
|
let etag = obj_success.prop.0.iter().find_map(|p| match p {
|
|
|
|
dav::AnyProperty::Value(dav::Property::GetEtag(x)) => Some(x.as_str()),
|
|
|
|
_ => None,
|
|
|
|
});
|
2024-05-23 08:01:43 +00:00
|
|
|
assert_eq!(etag, ref_etag);
|
2024-05-26 08:33:04 +00:00
|
|
|
let calendar_data = obj_success.prop.0.iter().find_map(|p| match p {
|
|
|
|
dav::AnyProperty::Value(dav::Property::Extension(
|
|
|
|
realization::Property::Cal(cal::Property::CalendarData(x)),
|
|
|
|
)) => Some(x.payload.as_bytes()),
|
|
|
|
_ => None,
|
|
|
|
});
|
2024-05-23 08:01:43 +00:00
|
|
|
assert_eq!(calendar_data, ref_ical);
|
2024-05-23 07:24:06 +00:00
|
|
|
};
|
|
|
|
|
2024-05-23 06:55:53 +00:00
|
|
|
// --- AUTODISCOVERY ---
|
2024-05-22 21:48:34 +00:00
|
|
|
// Check calendar discovery from principal
|
|
|
|
let propfind_req = r#"<?xml version="1.0" encoding="utf-8" ?>
|
|
|
|
<D:propfind xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
|
|
|
<D:prop><C:calendar-home-set/></D:prop>
|
|
|
|
</D:propfind>"#;
|
|
|
|
|
|
|
|
let body = http
|
|
|
|
.request(
|
|
|
|
reqwest::Method::from_bytes(b"PROPFIND")?,
|
|
|
|
"http://localhost:8087/alice/",
|
|
|
|
)
|
|
|
|
.body(propfind_req)
|
|
|
|
.send()?
|
|
|
|
.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
let principal_propstats = multistatus
|
|
|
|
.responses
|
|
|
|
.iter()
|
|
|
|
.find_map(|v| match &v.status_or_propstat {
|
|
|
|
dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/alice/" => {
|
|
|
|
Some(x)
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.expect("propstats for root must exist");
|
|
|
|
let principal_success = principal_propstats
|
|
|
|
.iter()
|
|
|
|
.find(|p| p.status.0.as_u16() == 200)
|
|
|
|
.expect("current-user-principal must exist");
|
|
|
|
let calendar_home_set = principal_success
|
|
|
|
.prop
|
|
|
|
.0
|
|
|
|
.iter()
|
|
|
|
.find_map(|v| match v {
|
|
|
|
dav::AnyProperty::Value(dav::Property::Extension(realization::Property::Cal(
|
|
|
|
cal::Property::CalendarHomeSet(dav::Href(x)),
|
|
|
|
))) => Some(x),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.expect("request returns a calendar home set");
|
|
|
|
assert_eq!(calendar_home_set, "/alice/calendar/");
|
|
|
|
|
2024-05-23 06:55:53 +00:00
|
|
|
// Check calendar access support
|
2024-05-23 07:24:06 +00:00
|
|
|
let _resp = http
|
2024-05-23 06:55:53 +00:00
|
|
|
.request(
|
|
|
|
reqwest::Method::from_bytes(b"OPTIONS")?,
|
|
|
|
"http://localhost:8087/alice/calendar/",
|
|
|
|
)
|
|
|
|
.send()?;
|
|
|
|
//@FIXME not yet supported. returns DAV: 1 ; expects DAV: 1 calendar-access
|
2024-05-25 17:30:59 +00:00
|
|
|
// Not used by any client I know, so not implementing it now.
|
2024-05-27 16:16:53 +00:00
|
|
|
|
2024-05-26 09:03:39 +00:00
|
|
|
// --- REPORT calendar-multiget ---
|
|
|
|
let cal_query = r#"<?xml version="1.0" encoding="utf-8" ?>
|
|
|
|
<C:calendar-multiget xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
|
|
|
<D:prop>
|
|
|
|
<D:getetag/>
|
|
|
|
<C:calendar-data/>
|
|
|
|
</D:prop>
|
|
|
|
<D:href>/alice/calendar/Personal/rfc1.ics</D:href>
|
|
|
|
<D:href>/alice/calendar/Personal/rfc3.ics</D:href>
|
|
|
|
</C:calendar-multiget>"#;
|
|
|
|
let resp = http
|
|
|
|
.request(
|
|
|
|
reqwest::Method::from_bytes(b"REPORT")?,
|
|
|
|
"http://localhost:8087/alice/calendar/Personal/",
|
|
|
|
)
|
|
|
|
.body(cal_query)
|
|
|
|
.send()?;
|
|
|
|
assert_eq!(resp.status(), 207);
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&resp.text()?);
|
|
|
|
assert_eq!(multistatus.responses.len(), 2);
|
|
|
|
[
|
|
|
|
("/alice/calendar/Personal/rfc1.ics", obj1_etag, ICAL_RFC1),
|
|
|
|
("/alice/calendar/Personal/rfc3.ics", obj3_etag, ICAL_RFC3),
|
|
|
|
]
|
|
|
|
.iter()
|
|
|
|
.for_each(|(ref_path, ref_etag, ref_ical)| {
|
|
|
|
check_cal(
|
|
|
|
&multistatus,
|
|
|
|
(
|
|
|
|
ref_path,
|
|
|
|
Some(ref_etag.to_str().expect("etag header convertible to str")),
|
|
|
|
Some(ref_ical),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
});
|
2024-05-23 06:55:53 +00:00
|
|
|
|
2024-05-26 09:03:39 +00:00
|
|
|
// --- REPORT calendar-query, only filtering ---
|
2024-05-23 06:55:53 +00:00
|
|
|
// 7.8.8. Example: Retrieval of Events Only
|
|
|
|
let cal_query = r#"<?xml version="1.0" encoding="utf-8" ?>
|
|
|
|
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
|
|
|
|
<D:prop xmlns:D="DAV:">
|
|
|
|
<D:getetag/>
|
|
|
|
<C:calendar-data/>
|
|
|
|
</D:prop>
|
|
|
|
<C:filter>
|
|
|
|
<C:comp-filter name="VCALENDAR">
|
|
|
|
<C:comp-filter name="VEVENT"/>
|
|
|
|
</C:comp-filter>
|
|
|
|
</C:filter>
|
|
|
|
</C:calendar-query>"#;
|
|
|
|
let resp = http
|
|
|
|
.request(
|
|
|
|
reqwest::Method::from_bytes(b"REPORT")?,
|
|
|
|
"http://localhost:8087/alice/calendar/Personal/",
|
|
|
|
)
|
|
|
|
.body(cal_query)
|
|
|
|
.send()?;
|
|
|
|
assert_eq!(resp.status(), 207);
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&resp.text()?);
|
2024-05-26 09:03:39 +00:00
|
|
|
assert_eq!(multistatus.responses.len(), 4);
|
2024-05-22 21:48:34 +00:00
|
|
|
|
2024-05-23 06:55:53 +00:00
|
|
|
[
|
|
|
|
("/alice/calendar/Personal/rfc1.ics", obj1_etag, ICAL_RFC1),
|
|
|
|
("/alice/calendar/Personal/rfc2.ics", obj2_etag, ICAL_RFC2),
|
|
|
|
("/alice/calendar/Personal/rfc3.ics", obj3_etag, ICAL_RFC3),
|
2024-05-26 09:03:39 +00:00
|
|
|
("/alice/calendar/Personal/rfc7.ics", obj7_etag, ICAL_RFC7),
|
2024-05-23 06:55:53 +00:00
|
|
|
]
|
|
|
|
.iter()
|
|
|
|
.for_each(|(ref_path, ref_etag, ref_ical)| {
|
2024-05-23 08:01:43 +00:00
|
|
|
check_cal(
|
2024-05-23 07:24:06 +00:00
|
|
|
&multistatus,
|
|
|
|
(
|
|
|
|
ref_path,
|
2024-05-23 08:01:43 +00:00
|
|
|
Some(ref_etag.to_str().expect("etag header convertible to str")),
|
|
|
|
Some(ref_ical),
|
2024-05-23 07:24:06 +00:00
|
|
|
),
|
|
|
|
)
|
|
|
|
});
|
|
|
|
|
2024-05-23 08:01:43 +00:00
|
|
|
// 8.2.1.2. Synchronize by Time Range (here: July 2006)
|
2024-05-25 17:30:59 +00:00
|
|
|
let cal_query = r#"<?xml version="1.0" encoding="utf-8" ?>
|
2024-05-23 08:01:43 +00:00
|
|
|
<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
|
|
|
<D:prop>
|
|
|
|
<D:getetag/>
|
|
|
|
</D:prop>
|
|
|
|
<C:filter>
|
|
|
|
<C:comp-filter name="VCALENDAR">
|
|
|
|
<C:comp-filter name="VEVENT">
|
|
|
|
<C:time-range start="20060701T000000Z" end="20060801T000000Z"/>
|
|
|
|
</C:comp-filter>
|
|
|
|
</C:comp-filter>
|
|
|
|
</C:filter>
|
|
|
|
</C:calendar-query>"#;
|
|
|
|
let resp = http
|
|
|
|
.request(
|
|
|
|
reqwest::Method::from_bytes(b"REPORT")?,
|
|
|
|
"http://localhost:8087/alice/calendar/Personal/",
|
|
|
|
)
|
|
|
|
.body(cal_query)
|
|
|
|
.send()?;
|
|
|
|
assert_eq!(resp.status(), 207);
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&resp.text()?);
|
|
|
|
assert_eq!(multistatus.responses.len(), 1);
|
2024-05-26 08:33:04 +00:00
|
|
|
check_cal(
|
|
|
|
&multistatus,
|
|
|
|
(
|
|
|
|
"/alice/calendar/Personal/rfc2.ics",
|
|
|
|
Some(obj2_etag.to_str().expect("etag header convertible to str")),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
);
|
2024-05-23 08:01:43 +00:00
|
|
|
|
2024-05-25 17:30:59 +00:00
|
|
|
// 7.8.5. Example: Retrieval of To-Dos by Alarm Time Range
|
|
|
|
let cal_query = r#"<?xml version="1.0" encoding="utf-8" ?>
|
|
|
|
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
|
|
|
|
<D:prop xmlns:D="DAV:">
|
|
|
|
<D:getetag/>
|
|
|
|
<C:calendar-data/>
|
|
|
|
</D:prop>
|
|
|
|
<C:filter>
|
|
|
|
<C:comp-filter name="VCALENDAR">
|
|
|
|
<C:comp-filter name="VTODO">
|
|
|
|
<C:comp-filter name="VALARM">
|
2024-05-26 08:33:04 +00:00
|
|
|
<C:time-range start="20060201T000000Z" end="20060301T000000Z"/>
|
2024-05-25 17:30:59 +00:00
|
|
|
</C:comp-filter>
|
|
|
|
</C:comp-filter>
|
|
|
|
</C:comp-filter>
|
|
|
|
</C:filter>
|
|
|
|
</C:calendar-query>"#;
|
|
|
|
let resp = http
|
|
|
|
.request(
|
|
|
|
reqwest::Method::from_bytes(b"REPORT")?,
|
|
|
|
"http://localhost:8087/alice/calendar/Personal/",
|
|
|
|
)
|
|
|
|
.body(cal_query)
|
|
|
|
.send()?;
|
|
|
|
assert_eq!(resp.status(), 207);
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&resp.text()?);
|
2024-05-26 08:33:04 +00:00
|
|
|
assert_eq!(multistatus.responses.len(), 1);
|
2024-05-26 09:03:39 +00:00
|
|
|
check_cal(
|
|
|
|
&multistatus,
|
|
|
|
(
|
|
|
|
"/alice/calendar/Personal/rfc6.ics",
|
|
|
|
Some(obj6_etag.to_str().expect("etag header convertible to str")),
|
|
|
|
Some(ICAL_RFC6),
|
|
|
|
),
|
|
|
|
);
|
2024-05-25 17:30:59 +00:00
|
|
|
|
|
|
|
// 7.8.6. Example: Retrieval of Event by UID
|
2024-05-26 09:03:39 +00:00
|
|
|
let cal_query = r#"<?xml version="1.0" encoding="utf-8" ?>
|
|
|
|
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
|
|
|
|
<D:prop xmlns:D="DAV:">
|
|
|
|
<D:getetag/>
|
|
|
|
<C:calendar-data/>
|
|
|
|
</D:prop>
|
|
|
|
<C:filter>
|
|
|
|
<C:comp-filter name="VCALENDAR">
|
|
|
|
<C:comp-filter name="VEVENT">
|
|
|
|
<C:prop-filter name="UID">
|
|
|
|
<C:text-match collation="i;octet">DC6C50A017428C5216A2F1CD@example.com</C:text-match>
|
|
|
|
</C:prop-filter>
|
|
|
|
</C:comp-filter>
|
|
|
|
</C:comp-filter>
|
|
|
|
</C:filter>
|
|
|
|
</C:calendar-query>"#;
|
|
|
|
let resp = http
|
|
|
|
.request(
|
|
|
|
reqwest::Method::from_bytes(b"REPORT")?,
|
|
|
|
"http://localhost:8087/alice/calendar/Personal/",
|
|
|
|
)
|
|
|
|
.body(cal_query)
|
|
|
|
.send()?;
|
|
|
|
assert_eq!(resp.status(), 207);
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&resp.text()?);
|
|
|
|
assert_eq!(multistatus.responses.len(), 1);
|
|
|
|
check_cal(
|
|
|
|
&multistatus,
|
|
|
|
(
|
|
|
|
"/alice/calendar/Personal/rfc3.ics",
|
|
|
|
Some(obj3_etag.to_str().expect("etag header convertible to str")),
|
|
|
|
Some(ICAL_RFC3),
|
|
|
|
),
|
|
|
|
);
|
2024-05-26 08:33:04 +00:00
|
|
|
|
2024-05-25 17:30:59 +00:00
|
|
|
|
2024-05-26 09:03:39 +00:00
|
|
|
// 7.8.7. Example: Retrieval of Events by PARTSTAT
|
2024-05-23 07:24:06 +00:00
|
|
|
let cal_query = r#"<?xml version="1.0" encoding="utf-8" ?>
|
2024-05-26 09:03:39 +00:00
|
|
|
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
|
|
|
|
<D:prop xmlns:D="DAV:">
|
2024-05-23 07:24:06 +00:00
|
|
|
<D:getetag/>
|
|
|
|
<C:calendar-data/>
|
|
|
|
</D:prop>
|
2024-05-26 09:03:39 +00:00
|
|
|
<C:filter>
|
|
|
|
<C:comp-filter name="VCALENDAR">
|
|
|
|
<C:comp-filter name="VEVENT">
|
|
|
|
<C:prop-filter name="ATTENDEE">
|
|
|
|
<C:text-match collation="i;ascii-casemap">mailto:lisa@example.com</C:text-match>
|
|
|
|
<C:param-filter name="PARTSTAT">
|
|
|
|
<C:text-match collation="i;ascii-casemap">NEEDS-ACTION</C:text-match>
|
|
|
|
</C:param-filter>
|
|
|
|
</C:prop-filter>
|
|
|
|
</C:comp-filter>
|
|
|
|
</C:comp-filter>
|
|
|
|
</C:filter>
|
|
|
|
</C:calendar-query>"#;
|
2024-05-23 07:24:06 +00:00
|
|
|
let resp = http
|
|
|
|
.request(
|
|
|
|
reqwest::Method::from_bytes(b"REPORT")?,
|
|
|
|
"http://localhost:8087/alice/calendar/Personal/",
|
|
|
|
)
|
|
|
|
.body(cal_query)
|
|
|
|
.send()?;
|
|
|
|
assert_eq!(resp.status(), 207);
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&resp.text()?);
|
2024-05-26 09:03:39 +00:00
|
|
|
assert_eq!(multistatus.responses.len(), 1);
|
|
|
|
check_cal(
|
|
|
|
&multistatus,
|
|
|
|
(
|
|
|
|
"/alice/calendar/Personal/rfc7.ics",
|
|
|
|
Some(obj7_etag.to_str().expect("etag header convertible to str")),
|
|
|
|
Some(ICAL_RFC7),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
// 7.8.9. Example: Retrieval of All Pending To-Dos
|
2024-05-26 09:07:24 +00:00
|
|
|
let cal_query = r#"<?xml version="1.0" encoding="utf-8" ?>
|
|
|
|
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
|
|
|
|
<D:prop xmlns:D="DAV:">
|
|
|
|
<D:getetag/>
|
|
|
|
<C:calendar-data/>
|
|
|
|
</D:prop>
|
|
|
|
<C:filter>
|
|
|
|
<C:comp-filter name="VCALENDAR">
|
|
|
|
<C:comp-filter name="VTODO">
|
|
|
|
<C:prop-filter name="COMPLETED">
|
|
|
|
<C:is-not-defined/>
|
|
|
|
</C:prop-filter>
|
|
|
|
<C:prop-filter name="STATUS">
|
|
|
|
<C:text-match negate-condition="yes">CANCELLED</C:text-match>
|
|
|
|
</C:prop-filter>
|
|
|
|
</C:comp-filter>
|
|
|
|
</C:comp-filter>
|
|
|
|
</C:filter>
|
|
|
|
</C:calendar-query>"#;
|
|
|
|
let resp = http
|
|
|
|
.request(
|
|
|
|
reqwest::Method::from_bytes(b"REPORT")?,
|
|
|
|
"http://localhost:8087/alice/calendar/Personal/",
|
|
|
|
)
|
|
|
|
.body(cal_query)
|
|
|
|
.send()?;
|
|
|
|
assert_eq!(resp.status(), 207);
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&resp.text()?);
|
|
|
|
assert_eq!(multistatus.responses.len(), 1);
|
|
|
|
check_cal(
|
|
|
|
&multistatus,
|
|
|
|
(
|
|
|
|
"/alice/calendar/Personal/rfc6.ics",
|
|
|
|
Some(obj6_etag.to_str().expect("etag header convertible to str")),
|
|
|
|
Some(ICAL_RFC6),
|
|
|
|
),
|
|
|
|
);
|
2024-05-26 09:03:39 +00:00
|
|
|
|
|
|
|
// --- REPORT calendar-query, with calendar-data tx ---
|
2024-05-27 06:03:21 +00:00
|
|
|
let cal_query = r#"<?xml version="1.0" encoding="utf-8" ?>
|
|
|
|
<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
|
|
|
<D:prop>
|
|
|
|
<D:getetag/>
|
|
|
|
<C:calendar-data>
|
|
|
|
<C:comp name="VCALENDAR">
|
|
|
|
<C:prop name="VERSION"/>
|
|
|
|
<C:comp name="VEVENT">
|
|
|
|
<C:prop name="UID"/>
|
|
|
|
<C:prop name="DTSTART"/>
|
|
|
|
<C:prop name="DTEND"/>
|
|
|
|
<C:prop name="DURATION"/>
|
|
|
|
<C:prop name="RRULE"/>
|
|
|
|
<C:prop name="RDATE"/>
|
|
|
|
<C:prop name="EXRULE"/>
|
|
|
|
<C:prop name="EXDATE"/>
|
|
|
|
<C:prop name="RECURRENCE-ID"/>
|
|
|
|
</C:comp>
|
|
|
|
<C:comp name="VTIMEZONE"/>
|
|
|
|
</C:comp>
|
|
|
|
</C:calendar-data>
|
|
|
|
</D:prop>
|
|
|
|
<C:filter>
|
|
|
|
<C:comp-filter name="VCALENDAR">
|
|
|
|
<C:comp-filter name="VEVENT">
|
|
|
|
<C:time-range start="20060104T000000Z" end="20060105T000000Z"/>
|
|
|
|
</C:comp-filter>
|
|
|
|
</C:comp-filter>
|
|
|
|
</C:filter>
|
|
|
|
</C:calendar-query>"#;
|
|
|
|
|
|
|
|
let resp = http
|
|
|
|
.request(
|
|
|
|
reqwest::Method::from_bytes(b"REPORT")?,
|
|
|
|
"http://localhost:8087/alice/calendar/Personal/",
|
|
|
|
)
|
|
|
|
.body(cal_query)
|
|
|
|
.send()?;
|
|
|
|
assert_eq!(resp.status(), 207);
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&resp.text()?);
|
|
|
|
assert_eq!(multistatus.responses.len(), 1);
|
|
|
|
check_cal(
|
|
|
|
&multistatus,
|
|
|
|
(
|
|
|
|
"/alice/calendar/Personal/rfc3.ics",
|
|
|
|
Some(obj3_etag.to_str().expect("etag header convertible to str")),
|
|
|
|
Some(ICAL_RFC3_STRIPPED),
|
|
|
|
),
|
|
|
|
);
|
2024-05-22 21:48:34 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.expect("test fully run")
|
|
|
|
}
|
2024-05-22 17:36:27 +00:00
|
|
|
|
2024-05-28 14:03:25 +00:00
|
|
|
fn rfc6578_webdav_sync() {
|
|
|
|
println!("🧪 rfc6578_webdav_sync");
|
|
|
|
common::aerogramme_provider_daemon_dev(|_imap, _lmtp, http| {
|
|
|
|
// propname on a calendar node must return <sync-token/> + <supported-report-set/> (2nd element is theoretically from versioning)
|
|
|
|
let propfind_req = r#"<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><propname/></propfind>"#;
|
|
|
|
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087/alice/calendar/Personal/").body(propfind_req).send()?.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
let root_propstats = multistatus.responses.iter()
|
|
|
|
.find_map(|v| match &v.status_or_propstat {
|
|
|
|
dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/alice/calendar/Personal/" => Some(x),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.expect("propstats for target must exist");
|
|
|
|
let root_success = root_propstats.iter().find(|p| p.status.0.as_u16() == 200).expect("some propstats for root must be 200");
|
|
|
|
assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Request(dav::PropertyRequest::Extension(
|
|
|
|
realization::PropertyRequest::Sync(sync::PropertyRequest::SyncToken)
|
|
|
|
)))).is_some());
|
|
|
|
assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Request(dav::PropertyRequest::Extension(
|
|
|
|
realization::PropertyRequest::Vers(vers::PropertyRequest::SupportedReportSet)
|
|
|
|
)))).is_some());
|
|
|
|
|
|
|
|
// synctoken and supported report set must contains a meaningful value when queried
|
|
|
|
let propfind_req = r#"<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><prop><sync-token/><supported-report-set/></prop></propfind>"#;
|
|
|
|
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087/alice/calendar/Personal/").body(propfind_req).send()?.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
let root_propstats = multistatus.responses.iter()
|
|
|
|
.find_map(|v| match &v.status_or_propstat {
|
|
|
|
dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/alice/calendar/Personal/" => Some(x),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.expect("propstats for target must exist");
|
|
|
|
|
|
|
|
let root_success = root_propstats.iter().find(|p| p.status.0.as_u16() == 200).expect("some propstats for root must be 200");
|
|
|
|
|
|
|
|
let init_sync_token = root_success.prop.0.iter().find_map(|p| match p {
|
|
|
|
dav::AnyProperty::Value(dav::Property::Extension(realization::Property::Sync(sync::Property::SyncToken(st)))) => Some(st),
|
|
|
|
_ => None,
|
|
|
|
}).expect("sync_token exists");
|
|
|
|
|
|
|
|
let supported = root_success.prop.0.iter().find_map(|p| match p {
|
|
|
|
dav::AnyProperty::Value(dav::Property::Extension(realization::Property::Vers(vers::Property::SupportedReportSet(s)))) => Some(s),
|
|
|
|
_ => None
|
|
|
|
}).expect("supported report set exists");
|
|
|
|
assert_eq!(&supported[..], &[
|
|
|
|
vers::SupportedReport(vers::ReportName::Extension(realization::ReportTypeName::Cal(cal::ReportTypeName::Multiget))),
|
|
|
|
vers::SupportedReport(vers::ReportName::Extension(realization::ReportTypeName::Cal(cal::ReportTypeName::Query))),
|
|
|
|
vers::SupportedReport(vers::ReportName::Extension(realization::ReportTypeName::Sync(sync::ReportTypeName::SyncCollection))),
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
// synctoken must change if we add a file
|
|
|
|
let resp = http
|
|
|
|
.put("http://localhost:8087/alice/calendar/Personal/rfc1.ics")
|
|
|
|
.header("If-None-Match", "*")
|
|
|
|
.body(ICAL_RFC1)
|
|
|
|
.send()?;
|
|
|
|
assert_eq!(resp.status(), 201);
|
|
|
|
|
|
|
|
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087/alice/calendar/Personal/").body(propfind_req).send()?.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
|
|
|
|
let root_propstats = multistatus.responses.iter()
|
|
|
|
.find_map(|v| match &v.status_or_propstat {
|
|
|
|
dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/alice/calendar/Personal/" => Some(x),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.expect("propstats for target must exist");
|
|
|
|
let root_success = root_propstats.iter().find(|p| p.status.0.as_u16() == 200).expect("some propstats for root must be 200");
|
|
|
|
let rfc1_sync_token = root_success.prop.0.iter().find_map(|p| match p {
|
|
|
|
dav::AnyProperty::Value(dav::Property::Extension(realization::Property::Sync(sync::Property::SyncToken(st)))) => Some(st),
|
|
|
|
_ => None,
|
|
|
|
}).expect("sync_token exists");
|
|
|
|
assert!(init_sync_token != rfc1_sync_token);
|
|
|
|
|
|
|
|
|
|
|
|
// synctoken must change if we delete a file
|
|
|
|
let resp = http.delete("http://localhost:8087/alice/calendar/Personal/rfc1.ics").send()?;
|
|
|
|
assert_eq!(resp.status(), 204);
|
|
|
|
|
|
|
|
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087/alice/calendar/Personal/").body(propfind_req).send()?.text()?;
|
|
|
|
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
|
|
|
|
|
|
|
|
let root_propstats = multistatus.responses.iter()
|
|
|
|
.find_map(|v| match &v.status_or_propstat {
|
|
|
|
dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/alice/calendar/Personal/" => Some(x),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.expect("propstats for target must exist");
|
|
|
|
let root_success = root_propstats.iter().find(|p| p.status.0.as_u16() == 200).expect("some propstats for root must be 200");
|
|
|
|
let del_sync_token = root_success.prop.0.iter().find_map(|p| match p {
|
|
|
|
dav::AnyProperty::Value(dav::Property::Extension(realization::Property::Sync(sync::Property::SyncToken(st)))) => Some(st),
|
|
|
|
_ => None,
|
|
|
|
}).expect("sync_token exists");
|
|
|
|
assert!(init_sync_token != del_sync_token);
|
|
|
|
assert!(rfc1_sync_token != del_sync_token);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.expect("test fully run")
|
|
|
|
}
|