aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQuentin Dufour <quentin@deuxfleurs.fr>2024-05-16 17:38:34 +0200
committerQuentin Dufour <quentin@deuxfleurs.fr>2024-05-16 17:38:34 +0200
commit32dfd25f570b7a55bf43752684d286be0f6b2dc2 (patch)
treedd77871cda851bb5795743a3f04be61cf4c3ad61
parent6b9542088cd1b66af46e95b787493b601accb495 (diff)
downloadaerogramme-32dfd25f570b7a55bf43752684d286be0f6b2dc2.tar.gz
aerogramme-32dfd25f570b7a55bf43752684d286be0f6b2dc2.zip
format + WIP calendar-query
-rw-r--r--Cargo.lock1
-rw-r--r--aero-collections/src/calendar/mod.rs17
-rw-r--r--aero-collections/src/calendar/namespace.rs43
-rw-r--r--aero-collections/src/davdag.rs39
-rw-r--r--aero-collections/src/lib.rs6
-rw-r--r--aero-collections/src/mail/incoming.rs4
-rw-r--r--aero-collections/src/mail/mailbox.rs6
-rw-r--r--aero-collections/src/mail/mod.rs2
-rw-r--r--aero-collections/src/mail/namespace.rs6
-rw-r--r--aero-collections/src/mail/snapshot.rs2
-rw-r--r--aero-collections/src/mail/uidindex.rs2
-rw-r--r--aero-collections/src/user.rs7
-rw-r--r--aero-dav/fuzz/fuzz_targets/dav.rs157
-rw-r--r--aero-dav/src/acldecoder.rs44
-rw-r--r--aero-dav/src/aclencoder.rs12
-rw-r--r--aero-dav/src/acltypes.rs2
-rw-r--r--aero-dav/src/caldecoder.rs696
-rw-r--r--aero-dav/src/calencoder.rs431
-rw-r--r--aero-dav/src/caltypes.rs53
-rw-r--r--aero-dav/src/decoder.rs674
-rw-r--r--aero-dav/src/encoder.rs435
-rw-r--r--aero-dav/src/error.rs4
-rw-r--r--aero-dav/src/lib.rs12
-rw-r--r--aero-dav/src/realization.rs38
-rw-r--r--aero-dav/src/types.rs29
-rw-r--r--aero-dav/src/xml.rs124
-rw-r--r--aero-proto/Cargo.toml1
-rw-r--r--aero-proto/src/dav/codec.rs71
-rw-r--r--aero-proto/src/dav/controller.rs200
-rw-r--r--aero-proto/src/dav/middleware.rs28
-rw-r--r--aero-proto/src/dav/mod.rs61
-rw-r--r--aero-proto/src/dav/node.rs68
-rw-r--r--aero-proto/src/dav/resource.rs565
-rw-r--r--aero-proto/src/imap/command/anonymous.rs2
-rw-r--r--aero-proto/src/imap/command/authenticated.rs4
-rw-r--r--aero-proto/src/imap/mod.rs6
-rw-r--r--aero-proto/src/lmtp.rs6
-rw-r--r--aero-proto/src/sasl.rs2
-rw-r--r--aero-sasl/src/flow.rs18
-rw-r--r--aero-sasl/src/lib.rs6
-rw-r--r--aero-sasl/src/types.rs2
-rw-r--r--aero-user/src/config.rs1
-rw-r--r--aero-user/src/login/ldap_provider.rs2
-rw-r--r--aero-user/src/storage/in_memory.rs2
-rw-r--r--aerogramme/src/main.rs2
-rw-r--r--aerogramme/src/server.rs8
46 files changed, 2454 insertions, 1447 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 484d96e..c6602ab 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -90,6 +90,7 @@ dependencies = [
"http-body-util",
"hyper 1.2.0",
"hyper-util",
+ "icalendar",
"imap-codec",
"imap-flow",
"quick-xml",
diff --git a/aero-collections/src/calendar/mod.rs b/aero-collections/src/calendar/mod.rs
index 028cf87..cd05328 100644
--- a/aero-collections/src/calendar/mod.rs
+++ b/aero-collections/src/calendar/mod.rs
@@ -4,12 +4,12 @@ use anyhow::{anyhow, bail, Result};
use tokio::sync::RwLock;
use aero_bayou::Bayou;
-use aero_user::login::Credentials;
use aero_user::cryptoblob::{self, gen_key, Key};
+use aero_user::login::Credentials;
use aero_user::storage::{self, BlobRef, BlobVal, Store};
+use crate::davdag::{BlobId, DavDag, IndexEntry, SyncChange, Token};
use crate::unique_ident::*;
-use crate::davdag::{DavDag, IndexEntry, Token, BlobId, SyncChange};
pub struct Calendar {
pub(super) id: UniqueIdent,
@@ -17,10 +17,7 @@ pub struct Calendar {
}
impl Calendar {
- pub(crate) async fn open(
- creds: &Credentials,
- id: UniqueIdent,
- ) -> Result<Self> {
+ pub(crate) async fn open(creds: &Credentials, id: UniqueIdent) -> Result<Self> {
let bayou_path = format!("calendar/dag/{}", id);
let cal_path = format!("calendar/events/{}", id);
@@ -126,7 +123,7 @@ impl CalendarInternal {
async fn put<'a>(&mut self, name: &str, evt: &'a [u8]) -> Result<(Token, IndexEntry)> {
let message_key = gen_key();
let blob_id = gen_ident();
-
+
let encrypted_msg_key = cryptoblob::seal(&message_key.as_ref(), &self.encryption_key)?;
let key_header = base64::engine::general_purpose::STANDARD.encode(&encrypted_msg_key);
@@ -138,9 +135,7 @@ impl CalendarInternal {
)
.with_meta(MESSAGE_KEY.to_string(), key_header);
- let etag = self.storage
- .blob_insert(blob_val)
- .await?;
+ let etag = self.storage.blob_insert(blob_val).await?;
// Add entry to Bayou
let entry: IndexEntry = (blob_id, name.to_string(), etag);
@@ -181,7 +176,7 @@ impl CalendarInternal {
let heads = davstate.heads_vec();
let token = match heads.as_slice() {
- [ token ] => *token,
+ [token] => *token,
_ => {
let op_mg = davstate.op_merge();
let token = op_mg.token();
diff --git a/aero-collections/src/calendar/namespace.rs b/aero-collections/src/calendar/namespace.rs
index 9c21d19..db65703 100644
--- a/aero-collections/src/calendar/namespace.rs
+++ b/aero-collections/src/calendar/namespace.rs
@@ -1,16 +1,16 @@
use anyhow::{bail, Result};
-use std::collections::{HashMap, BTreeMap};
-use std::sync::{Weak, Arc};
+use std::collections::{BTreeMap, HashMap};
+use std::sync::{Arc, Weak};
use serde::{Deserialize, Serialize};
use aero_bayou::timestamp::now_msec;
-use aero_user::storage;
use aero_user::cryptoblob::{open_deserialize, seal_serialize};
+use aero_user::storage;
+use super::Calendar;
use crate::unique_ident::{gen_ident, UniqueIdent};
use crate::user::User;
-use super::Calendar;
pub(crate) const CAL_LIST_PK: &str = "calendars";
pub(crate) const CAL_LIST_SK: &str = "list";
@@ -46,7 +46,7 @@ impl CalendarNs {
}
let cal = Arc::new(Calendar::open(&user.creds, id).await?);
-
+
let mut cache = self.0.lock().unwrap();
if let Some(concurrent_cal) = cache.get(&id).and_then(Weak::upgrade) {
drop(cal); // we worked for nothing but at least we didn't starve someone else
@@ -117,13 +117,15 @@ impl CalendarNs {
CalendarExists::Created(_) => (),
}
list.save(user, ct).await?;
-
+
Ok(())
}
/// Has calendar
pub async fn has(&self, user: &Arc<User>, name: &str) -> Result<bool> {
- CalendarList::load(user).await.map(|(list, _)| list.has(name))
+ CalendarList::load(user)
+ .await
+ .map(|(list, _)| list.has(name))
}
}
@@ -161,7 +163,8 @@ impl CalendarList {
for v in row_vals {
if let storage::Alternative::Value(vbytes) = v {
- let list2 = open_deserialize::<CalendarList>(&vbytes, &user.creds.keys.master)?;
+ let list2 =
+ open_deserialize::<CalendarList>(&vbytes, &user.creds.keys.master)?;
list.merge(list2);
}
}
@@ -200,7 +203,7 @@ impl CalendarList {
/// (Don't forget to save if it returns CalendarExists::Created)
fn create(&mut self, name: &str) -> CalendarExists {
if let Some(CalendarListEntry {
- id_lww: (_, Some(id))
+ id_lww: (_, Some(id)),
}) = self.0.get(name)
{
return CalendarExists::Existed(*id);
@@ -222,9 +225,10 @@ impl CalendarList {
/// For a given calendar name, get its Unique Identifier
fn get(&self, name: &str) -> Option<UniqueIdent> {
- self.0.get(name).map(|CalendarListEntry {
- id_lww: (_, ident),
- }| *ident).flatten()
+ self.0
+ .get(name)
+ .map(|CalendarListEntry { id_lww: (_, ident) }| *ident)
+ .flatten()
}
/// Check if a given calendar name exists
@@ -271,9 +275,7 @@ impl CalendarList {
(now_msec(), id)
}
}
- Some(CalendarListEntry {
- id_lww,
- }) => {
+ Some(CalendarListEntry { id_lww }) => {
if id_lww.1 == id {
// Entry is already equals to the requested id (Option<UniqueIdent)
// Nothing to do
@@ -281,20 +283,15 @@ impl CalendarList {
} else {
// Entry does not equal to what we know internally
// We update the Last Write Win CRDT here with the new id value
- (
- std::cmp::max(id_lww.0 + 1, now_msec()),
- id,
- )
+ (std::cmp::max(id_lww.0 + 1, now_msec()), id)
}
}
};
// If we did not return here, that's because we have to update
// something in our internal index.
- self.0.insert(
- name.into(),
- CalendarListEntry { id_lww: (ts, id) },
- );
+ self.0
+ .insert(name.into(), CalendarListEntry { id_lww: (ts, id) });
Some(())
}
diff --git a/aero-collections/src/davdag.rs b/aero-collections/src/davdag.rs
index 7335bdc..36a9016 100644
--- a/aero-collections/src/davdag.rs
+++ b/aero-collections/src/davdag.rs
@@ -1,6 +1,6 @@
use anyhow::{bail, Result};
+use im::{ordset, OrdMap, OrdSet};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
-use im::{OrdMap, OrdSet, ordset};
use aero_bayou::*;
@@ -26,7 +26,6 @@ pub struct DavDag {
pub idx_by_filename: OrdMap<FileName, BlobId>,
// ------------ Below this line, data is ephemeral, ie. not checkpointed
-
/// Partial synchronization graph
pub ancestors: OrdMap<Token, OrdSet<Token>>,
@@ -84,7 +83,7 @@ impl DavDag {
// HELPER functions
pub fn heads_vec(&self) -> Vec<Token> {
- self.heads.clone().into_iter().collect()
+ self.heads.clone().into_iter().collect()
}
/// A sync descriptor
@@ -99,7 +98,7 @@ impl DavDag {
// We can't capture all missing events if we are not connected
// to all sinks of the graph,
// ie. if we don't already know all the sinks,
- // ie. if we are missing so much history that
+ // ie. if we are missing so much history that
// the event log has been transformed into a checkpoint
if !self.origins.is_subset(already_known.clone()) {
bail!("Not enough history to produce a correct diff, a full resync is needed");
@@ -124,7 +123,7 @@ impl DavDag {
if all_known.insert(cursor).is_some() {
// Item already processed
- continue
+ continue;
}
// Collect parents
@@ -167,7 +166,8 @@ impl DavDag {
self.idx_by_filename.remove(filename);
// Record the change in the ephemeral synchronization map
- self.change.insert(sync_token, SyncChange::NotFound(filename.to_string()));
+ self.change
+ .insert(sync_token, SyncChange::NotFound(filename.to_string()));
// Finally clear item from the source of trust
self.table.remove(blob_id);
@@ -179,10 +179,13 @@ impl DavDag {
// --- Update ANCESTORS
// We register ancestors as it is required for the sync algorithm
- self.ancestors.insert(*child, parents.iter().fold(ordset![], |mut acc, p| {
- acc.insert(*p);
- acc
- }));
+ self.ancestors.insert(
+ *child,
+ parents.iter().fold(ordset![], |mut acc, p| {
+ acc.insert(*p);
+ acc
+ }),
+ );
// --- Update ORIGINS
// If this event has no parents, it's an origin
@@ -192,11 +195,13 @@ impl DavDag {
// --- Update HEADS
// Remove from HEADS this event's parents
- parents.iter().for_each(|par| { self.heads.remove(par); });
+ parents.iter().for_each(|par| {
+ self.heads.remove(par);
+ });
// This event becomes a new HEAD in turn
self.heads.insert(*child);
-
+
// --- Update ALL NODES
self.all_nodes.insert(*child);
}
@@ -217,16 +222,16 @@ impl BayouState for DavDag {
fn apply(&self, op: &Self::Op) -> Self {
let mut new = self.clone();
-
+
match op {
DavDagOp::Put(sync_desc, entry) => {
new.sync_dag(sync_desc);
new.register(Some(sync_desc.1), entry.clone());
- },
+ }
DavDagOp::Delete(sync_desc, blob_id) => {
new.sync_dag(sync_desc);
new.unregister(sync_desc.1, blob_id);
- },
+ }
DavDagOp::Merge(sync_desc) => {
new.sync_dag(sync_desc);
}
@@ -252,7 +257,9 @@ impl<'de> Deserialize<'de> for DavDag {
let mut davdag = DavDag::default();
// Build the table + index
- val.items.into_iter().for_each(|entry| davdag.register(None, entry));
+ val.items
+ .into_iter()
+ .for_each(|entry| davdag.register(None, entry));
// Initialize the synchronization DAG with its roots
val.heads.into_iter().for_each(|ident| {
diff --git a/aero-collections/src/lib.rs b/aero-collections/src/lib.rs
index ef8b8d8..eabf61c 100644
--- a/aero-collections/src/lib.rs
+++ b/aero-collections/src/lib.rs
@@ -1,5 +1,5 @@
-pub mod unique_ident;
+pub mod calendar;
pub mod davdag;
-pub mod user;
pub mod mail;
-pub mod calendar;
+pub mod unique_ident;
+pub mod user;
diff --git a/aero-collections/src/mail/incoming.rs b/aero-collections/src/mail/incoming.rs
index cd2f8fd..55c2515 100644
--- a/aero-collections/src/mail/incoming.rs
+++ b/aero-collections/src/mail/incoming.rs
@@ -8,16 +8,16 @@ use futures::{future::BoxFuture, FutureExt};
use tokio::sync::watch;
use tracing::{debug, error, info, warn};
+use aero_bayou::timestamp::now_msec;
use aero_user::cryptoblob;
use aero_user::login::{Credentials, PublicCredentials};
use aero_user::storage;
-use aero_bayou::timestamp::now_msec;
use crate::mail::mailbox::Mailbox;
use crate::mail::uidindex::ImapUidvalidity;
+use crate::mail::IMF;
use crate::unique_ident::*;
use crate::user::User;
-use crate::mail::IMF;
const INCOMING_PK: &str = "incoming";
const INCOMING_LOCK_SK: &str = "lock";
diff --git a/aero-collections/src/mail/mailbox.rs b/aero-collections/src/mail/mailbox.rs
index fcdb21e..bec9669 100644
--- a/aero-collections/src/mail/mailbox.rs
+++ b/aero-collections/src/mail/mailbox.rs
@@ -2,15 +2,15 @@ use anyhow::{anyhow, bail, Result};
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
+use aero_bayou::timestamp::now_msec;
+use aero_bayou::Bayou;
use aero_user::cryptoblob::{self, gen_key, open_deserialize, seal_serialize, Key};
use aero_user::login::Credentials;
use aero_user::storage::{self, BlobRef, BlobVal, RowRef, RowVal, Selector, Store};
-use aero_bayou::Bayou;
-use aero_bayou::timestamp::now_msec;
-use crate::unique_ident::*;
use crate::mail::uidindex::*;
use crate::mail::IMF;
+use crate::unique_ident::*;
pub struct Mailbox {
pub(super) id: UniqueIdent,
diff --git a/aero-collections/src/mail/mod.rs b/aero-collections/src/mail/mod.rs
index ca9b08b..584a9eb 100644
--- a/aero-collections/src/mail/mod.rs
+++ b/aero-collections/src/mail/mod.rs
@@ -1,9 +1,9 @@
pub mod incoming;
pub mod mailbox;
+pub mod namespace;
pub mod query;
pub mod snapshot;
pub mod uidindex;
-pub mod namespace;
// Internet Message Format
// aka RFC 822 - RFC 2822 - RFC 5322
diff --git a/aero-collections/src/mail/namespace.rs b/aero-collections/src/mail/namespace.rs
index b1f6a70..0f1db7d 100644
--- a/aero-collections/src/mail/namespace.rs
+++ b/aero-collections/src/mail/namespace.rs
@@ -104,7 +104,11 @@ impl MailboxList {
/// Ensures mailbox `name` maps to id `id`.
/// If it already mapped to that, returns None.
/// If a change had to be done, returns Some(new uidvalidity in mailbox).
- pub(crate) fn set_mailbox(&mut self, name: &str, id: Option<UniqueIdent>) -> Option<ImapUidvalidity> {
+ pub(crate) fn set_mailbox(
+ &mut self,
+ name: &str,
+ id: Option<UniqueIdent>,
+ ) -> Option<ImapUidvalidity> {
let (ts, id, uidvalidity) = match self.0.get_mut(name) {
None => {
if id.is_none() {
diff --git a/aero-collections/src/mail/snapshot.rs b/aero-collections/src/mail/snapshot.rs
index 9503d4d..6f8a8a8 100644
--- a/aero-collections/src/mail/snapshot.rs
+++ b/aero-collections/src/mail/snapshot.rs
@@ -2,10 +2,10 @@ use std::sync::Arc;
use anyhow::Result;
-use crate::unique_ident::UniqueIdent;
use super::mailbox::Mailbox;
use super::query::{Query, QueryScope};
use super::uidindex::UidIndex;
+use crate::unique_ident::UniqueIdent;
/// A Frozen Mailbox has a snapshot of the current mailbox
/// state that is desynchronized with the real mailbox state.
diff --git a/aero-collections/src/mail/uidindex.rs b/aero-collections/src/mail/uidindex.rs
index ca975a3..6df3206 100644
--- a/aero-collections/src/mail/uidindex.rs
+++ b/aero-collections/src/mail/uidindex.rs
@@ -3,8 +3,8 @@ use std::num::{NonZeroU32, NonZeroU64};
use im::{HashMap, OrdMap, OrdSet};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
-use aero_bayou::*;
use crate::unique_ident::UniqueIdent;
+use aero_bayou::*;
pub type ModSeq = NonZeroU64;
pub type ImapUid = NonZeroU32;
diff --git a/aero-collections/src/user.rs b/aero-collections/src/user.rs
index 9ed342f..f125c46 100644
--- a/aero-collections/src/user.rs
+++ b/aero-collections/src/user.rs
@@ -9,12 +9,15 @@ use aero_user::cryptoblob::{open_deserialize, seal_serialize};
use aero_user::login::Credentials;
use aero_user::storage;
+use crate::calendar::namespace::CalendarNs;
use crate::mail::incoming::incoming_mail_watch_process;
use crate::mail::mailbox::Mailbox;
+use crate::mail::namespace::{
+ CreatedMailbox, MailboxList, ARCHIVE, DRAFTS, INBOX, MAILBOX_HIERARCHY_DELIMITER,
+ MAILBOX_LIST_PK, MAILBOX_LIST_SK, SENT, TRASH,
+};
use crate::mail::uidindex::ImapUidvalidity;
use crate::unique_ident::UniqueIdent;
-use crate::mail::namespace::{MAILBOX_HIERARCHY_DELIMITER, INBOX, DRAFTS, ARCHIVE, SENT, TRASH, MAILBOX_LIST_PK, MAILBOX_LIST_SK,MailboxList,CreatedMailbox};
-use crate::calendar::namespace::CalendarNs;
//@FIXME User should be totally rewriten
// to extract the local mailbox list
diff --git a/aero-dav/fuzz/fuzz_targets/dav.rs b/aero-dav/fuzz/fuzz_targets/dav.rs
index 5bd28bc..a303401 100644
--- a/aero-dav/fuzz/fuzz_targets/dav.rs
+++ b/aero-dav/fuzz/fuzz_targets/dav.rs
@@ -1,79 +1,79 @@
#![no_main]
-use libfuzzer_sys::fuzz_target;
use libfuzzer_sys::arbitrary;
use libfuzzer_sys::arbitrary::Arbitrary;
+use libfuzzer_sys::fuzz_target;
-use aero_dav::{types, realization, xml};
+use aero_dav::{realization, types, xml};
use quick_xml::reader::NsReader;
-use tokio::runtime::Runtime;
use tokio::io::AsyncWriteExt;
+use tokio::runtime::Runtime;
// Split this file
const tokens: [&str; 63] = [
-"0",
-"1",
-"activelock",
-"allprop",
-"encoding",
-"utf-8",
-"http://ns.example.com/boxschema/",
-"HTTP/1.1 200 OK",
-"1997-12-01T18:27:21-08:00",
-"Mon, 12 Jan 1998 09:25:56 GMT",
-"\"abcdef\"",
-"cannot-modify-protected-property",
-"collection",
-"creationdate",
-"DAV:",
-"D",
-"C",
-"xmlns:D",
-"depth",
-"displayname",
-"error",
-"exclusive",
-"getcontentlanguage",
-"getcontentlength",
-"getcontenttype",
-"getetag",
-"getlastmodified",
-"href",
-"include",
-"Infinite",
-"infinity",
-"location",
-"lockdiscovery",
-"lockentry",
-"lockinfo",
-"lockroot",
-"lockscope",
-"locktoken",
-"lock-token-matches-request-uri",
-"lock-token-submitted",
-"locktype",
-"multistatus",
-"no-conflicting-lock",
-"no-external-entities",
-"owner",
-"preserved-live-properties",
-"prop",
-"propertyupdate",
-"propfind",
-"propfind-finite-depth",
-"propname",
-"propstat",
-"remove",
-"resourcetype",
-"response",
-"responsedescription",
-"set",
-"shared",
-"status",
-"supportedlock",
-"text/html",
-"timeout",
-"write",
+ "0",
+ "1",
+ "activelock",
+ "allprop",
+ "encoding",
+ "utf-8",
+ "http://ns.example.com/boxschema/",
+ "HTTP/1.1 200 OK",
+ "1997-12-01T18:27:21-08:00",
+ "Mon, 12 Jan 1998 09:25:56 GMT",
+ "\"abcdef\"",
+ "cannot-modify-protected-property",
+ "collection",
+ "creationdate",
+ "DAV:",
+ "D",
+ "C",
+ "xmlns:D",
+ "depth",
+ "displayname",
+ "error",
+ "exclusive",
+ "getcontentlanguage",
+ "getcontentlength",
+ "getcontenttype",
+ "getetag",
+ "getlastmodified",
+ "href",
+ "include",
+ "Infinite",
+ "infinity",
+ "location",
+ "lockdiscovery",
+ "lockentry",
+ "lockinfo",
+ "lockroot",
+ "lockscope",
+ "locktoken",
+ "lock-token-matches-request-uri",
+ "lock-token-submitted",
+ "locktype",
+ "multistatus",
+ "no-conflicting-lock",
+ "no-external-entities",
+ "owner",
+ "preserved-live-properties",
+ "prop",
+ "propertyupdate",
+ "propfind",
+ "propfind-finite-depth",
+ "propname",
+ "propstat",
+ "remove",
+ "resourcetype",
+ "response",
+ "responsedescription",
+ "set",
+ "shared",
+ "status",
+ "supportedlock",
+ "text/html",
+ "timeout",
+ "write",
];
#[derive(Arbitrary)]
@@ -106,7 +106,7 @@ impl Tag {
acc.push_str("D:");
acc.push_str(self.name.serialize().as_str());
- if let Some((k,v)) = &self.attr {
+ if let Some((k, v)) = &self.attr {
acc.push_str(" ");
acc.push_str(k.serialize().as_str());
acc.push_str("=\"");
@@ -123,7 +123,6 @@ impl Tag {
}
}
-
#[derive(Arbitrary)]
enum XmlNode {
//@FIXME: build RFC3339 and RFC822 Dates with chrono based on timestamps
@@ -145,9 +144,14 @@ impl XmlNode {
let stag = tag.start();
match children.is_empty() {
true => format!("<{}/>", stag),
- false => format!("<{}>{}</{}>", stag, children.iter().map(|v| v.serialize()).collect::<String>(), tag.end()),
+ false => format!(
+ "<{}>{}</{}>",
+ stag,
+ children.iter().map(|v| v.serialize()).collect::<String>(),
+ tag.end()
+ ),
}
- },
+ }
Self::Number(v) => format!("{}", v),
Self::Text(v) => v.serialize(),
}
@@ -158,19 +162,22 @@ async fn serialize(elem: &impl xml::QWrite) -> Vec<u8> {
let mut buffer = Vec::new();
let mut tokio_buffer = tokio::io::BufWriter::new(&mut buffer);
let q = quick_xml::writer::Writer::new_with_indent(&mut tokio_buffer, b' ', 4);
- let ns_to_apply = vec![ ("xmlns:D".into(), "DAV:".into()) ];
+ let ns_to_apply = vec![("xmlns:D".into(), "DAV:".into())];
let mut writer = xml::Writer { q, ns_to_apply };
elem.qwrite(&mut writer).await.expect("xml serialization");
tokio_buffer.flush().await.expect("tokio buffer flush");
- return buffer
+ return buffer;
}
type Object = types::Multistatus<realization::Core, types::PropValue<realization::Core>>;
fuzz_target!(|nodes: XmlNode| {
- let gen = format!("<D:multistatus xmlns:D=\"DAV:\">{}<D:/multistatus>", nodes.serialize());
+ let gen = format!(
+ "<D:multistatus xmlns:D=\"DAV:\">{}<D:/multistatus>",
+ nodes.serialize()
+ );
//println!("--------\n{}", gen);
let data = gen.as_bytes();
@@ -191,7 +198,9 @@ fuzz_target!(|nodes: XmlNode| {
let my_serialization = serialize(&reference).await;
// 3. De-serialize my serialization
- let mut rdr2 = xml::Reader::new(NsReader::from_reader(my_serialization.as_slice())).await.expect("XML Reader init");
+ let mut rdr2 = xml::Reader::new(NsReader::from_reader(my_serialization.as_slice()))
+ .await
+ .expect("XML Reader init");
let comparison = rdr2.find::<Object>().await.expect("Deserialize again");
// 4. Both the first decoding and last decoding must be identical
diff --git a/aero-dav/src/acldecoder.rs b/aero-dav/src/acldecoder.rs
index 67dfb0b..405286e 100644
--- a/aero-dav/src/acldecoder.rs
+++ b/aero-dav/src/acldecoder.rs
@@ -1,23 +1,31 @@
use super::acltypes::*;
-use super::types as dav;
-use super::xml::{QRead, Reader, IRead, DAV_URN};
use super::error::ParsingError;
+use super::types as dav;
+use super::xml::{IRead, QRead, Reader, DAV_URN};
impl QRead<Property> for Property {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
if xml.maybe_open_start(DAV_URN, "owner").await?.is_some() {
let href = xml.find().await?;
xml.close().await?;
- return Ok(Self::Owner(href))
+ return Ok(Self::Owner(href));
}
- if xml.maybe_open_start(DAV_URN, "current-user-principal").await?.is_some() {
+ if xml
+ .maybe_open_start(DAV_URN, "current-user-principal")
+ .await?
+ .is_some()
+ {
let user = xml.find().await?;
xml.close().await?;
- return Ok(Self::CurrentUserPrincipal(user))
+ return Ok(Self::CurrentUserPrincipal(user));
}
- if xml.maybe_open_start(DAV_URN, "current-user-privilege-set").await?.is_some() {
+ if xml
+ .maybe_open_start(DAV_URN, "current-user-privilege-set")
+ .await?
+ .is_some()
+ {
xml.close().await?;
- return Ok(Self::CurrentUserPrivilegeSet(vec![]))
+ return Ok(Self::CurrentUserPrivilegeSet(vec![]));
}
Err(ParsingError::Recoverable)
@@ -28,17 +36,25 @@ impl QRead<PropertyRequest> for PropertyRequest {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
if xml.maybe_open(DAV_URN, "owner").await?.is_some() {
xml.close().await?;
- return Ok(Self::Owner)
+ return Ok(Self::Owner);
}
- if xml.maybe_open(DAV_URN, "current-user-principal").await?.is_some() {
+ if xml
+ .maybe_open(DAV_URN, "current-user-principal")
+ .await?
+ .is_some()
+ {
xml.close().await?;
- return Ok(Self::CurrentUserPrincipal)
+ return Ok(Self::CurrentUserPrincipal);
}
- if xml.maybe_open(DAV_URN, "current-user-privilege-set").await?.is_some() {
+ if xml
+ .maybe_open(DAV_URN, "current-user-privilege-set")
+ .await?
+ .is_some()
+ {
xml.close().await?;
- return Ok(Self::CurrentUserPrivilegeSet)
+ return Ok(Self::CurrentUserPrivilegeSet);
}
Err(ParsingError::Recoverable)
@@ -49,7 +65,7 @@ impl QRead<ResourceType> for ResourceType {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
if xml.maybe_open(DAV_URN, "principal").await?.is_some() {
xml.close().await?;
- return Ok(Self::Principal)
+ return Ok(Self::Principal);
}
Err(ParsingError::Recoverable)
}
@@ -60,7 +76,7 @@ impl QRead<User> for User {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
if xml.maybe_open(DAV_URN, "unauthenticated").await?.is_some() {
xml.close().await?;
- return Ok(Self::Unauthenticated)
+ return Ok(Self::Unauthenticated);
}
dav::Href::qread(xml).await.map(Self::Authenticated)
diff --git a/aero-dav/src/aclencoder.rs b/aero-dav/src/aclencoder.rs
index 2fa4707..28c01a7 100644
--- a/aero-dav/src/aclencoder.rs
+++ b/aero-dav/src/aclencoder.rs
@@ -1,9 +1,9 @@
-use quick_xml::Error as QError;
use quick_xml::events::Event;
+use quick_xml::Error as QError;
use super::acltypes::*;
-use super::xml::{QWrite, Writer, IWrite};
use super::error::ParsingError;
+use super::xml::{IWrite, QWrite, Writer};
impl QWrite for Property {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
@@ -14,18 +14,18 @@ impl QWrite for Property {
xml.q.write_event_async(Event::Start(start.clone())).await?;
href.qwrite(xml).await?;
xml.q.write_event_async(Event::End(end)).await
- },
+ }
Self::CurrentUserPrincipal(user) => {
let start = xml.create_dav_element("current-user-principal");
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
user.qwrite(xml).await?;
xml.q.write_event_async(Event::End(end)).await
- },
+ }
Self::CurrentUserPrivilegeSet(_) => {
let empty_tag = xml.create_dav_element("current-user-privilege-set");
xml.q.write_event_async(Event::Empty(empty_tag)).await
- },
+ }
}
}
}
@@ -64,7 +64,7 @@ impl QWrite for User {
Self::Unauthenticated => {
let tag = xml.create_dav_element("unauthenticated");
xml.q.write_event_async(Event::Empty(tag)).await
- },
+ }
Self::Authenticated(href) => href.qwrite(xml).await,
}
}
diff --git a/aero-dav/src/acltypes.rs b/aero-dav/src/acltypes.rs
index d5be413..0af3c8a 100644
--- a/aero-dav/src/acltypes.rs
+++ b/aero-dav/src/acltypes.rs
@@ -2,14 +2,12 @@ use super::types as dav;
//RFC covered: RFC3744 (ACL core) + RFC5397 (ACL Current Principal Extension)
-
//@FIXME required for a full CalDAV implementation
// See section 6. of the CalDAV RFC
// It seems mainly required for free-busy that I will not implement now.
// It can also be used for discovering main calendar, not sure it is used.
// Note: it is used by Thunderbird
-
#[derive(Debug, PartialEq, Clone)]
pub enum PropertyRequest {
Owner,
diff --git a/aero-dav/src/caldecoder.rs b/aero-dav/src/caldecoder.rs
index 008668e..16c9c6c 100644
--- a/aero-dav/src/caldecoder.rs
+++ b/aero-dav/src/caldecoder.rs
@@ -1,10 +1,10 @@
-use quick_xml::events::Event;
use chrono::NaiveDateTime;
+use quick_xml::events::Event;
-use super::types as dav;
use super::caltypes::*;
-use super::xml::{QRead, IRead, Reader, DAV_URN, CAL_URN};
use super::error::ParsingError;
+use super::types as dav;
+use super::xml::{IRead, QRead, Reader, CAL_URN, DAV_URN};
// ---- ROOT ELEMENTS ---
impl<E: dav::Extension> QRead<MkCalendar<E>> for MkCalendar<E> {
@@ -29,7 +29,7 @@ impl<E: dav::Extension> QRead<Report<E>> for Report<E> {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
match CalendarQuery::<E>::qread(xml).await {
Err(ParsingError::Recoverable) => (),
- otherwise => return otherwise.map(Self::Query)
+ otherwise => return otherwise.map(Self::Query),
}
match CalendarMultiget::<E>::qread(xml).await {
@@ -61,7 +61,11 @@ impl<E: dav::Extension> QRead<CalendarQuery<E>> for CalendarQuery<E> {
xml.close().await?;
match filter {
- Some(filter) => Ok(CalendarQuery { selector, filter, timezone }),
+ Some(filter) => Ok(CalendarQuery {
+ selector,
+ filter,
+ timezone,
+ }),
_ => Err(ParsingError::MissingChild),
}
}
@@ -100,39 +104,70 @@ impl QRead<FreeBusyQuery> for FreeBusyQuery {
}
}
-
// ---- EXTENSIONS ---
impl QRead<Violation> for Violation {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
- if xml.maybe_open(DAV_URN, "resource-must-be-null").await?.is_some() {
+ if xml
+ .maybe_open(DAV_URN, "resource-must-be-null")
+ .await?
+ .is_some()
+ {
xml.close().await?;
Ok(Self::ResourceMustBeNull)
} else if xml.maybe_open(DAV_URN, "need-privileges").await?.is_some() {
xml.close().await?;
Ok(Self::NeedPrivileges)
- } else if xml.maybe_open(CAL_URN, "calendar-collection-location-ok").await?.is_some() {
+ } else if xml
+ .maybe_open(CAL_URN, "calendar-collection-location-ok")
+ .await?
+ .is_some()
+ {
xml.close().await?;
Ok(Self::CalendarCollectionLocationOk)
- } else if xml.maybe_open(CAL_URN, "valid-calendar-data").await?.is_some() {
+ } else if xml
+ .maybe_open(CAL_URN, "valid-calendar-data")
+ .await?
+ .is_some()
+ {
xml.close().await?;
Ok(Self::ValidCalendarData)
- } else if xml.maybe_open(CAL_URN, "initialize-calendar-collection").await?.is_some() {
+ } else if xml
+ .maybe_open(CAL_URN, "initialize-calendar-collection")
+ .await?
+ .is_some()
+ {
xml.close().await?;
Ok(Self::InitializeCalendarCollection)
- } else if xml.maybe_open(CAL_URN, "supported-calendar-data").await?.is_some() {
+ } else if xml
+ .maybe_open(CAL_URN, "supported-calendar-data")
+ .await?
+ .is_some()
+ {
xml.close().await?;
Ok(Self::SupportedCalendarData)
- } else if xml.maybe_open(CAL_URN, "valid-calendar-object-resource").await?.is_some() {
+ } else if xml
+ .maybe_open(CAL_URN, "valid-calendar-object-resource")
+ .await?
+ .is_some()
+ {
xml.close().await?;
Ok(Self::ValidCalendarObjectResource)
- } else if xml.maybe_open(CAL_URN, "supported-calendar-component").await?.is_some() {
+ } else if xml
+ .maybe_open(CAL_URN, "supported-calendar-component")
+ .await?
+ .is_some()
+ {
xml.close().await?;
Ok(Self::SupportedCalendarComponent)
} else if xml.maybe_open(CAL_URN, "no-uid-conflict").await?.is_some() {
let href = xml.find().await?;
xml.close().await?;
Ok(Self::NoUidConflict(href))
- } else if xml.maybe_open(CAL_URN, "max-resource-size").await?.is_some() {
+ } else if xml
+ .maybe_open(CAL_URN, "max-resource-size")
+ .await?
+ .is_some()
+ {
xml.close().await?;
Ok(Self::MaxResourceSize)
} else if xml.maybe_open(CAL_URN, "min-date-time").await?.is_some() {
@@ -144,7 +179,11 @@ impl QRead<Violation> for Violation {
} else if xml.maybe_open(CAL_URN, "max-instances").await?.is_some() {
xml.close().await?;
Ok(Self::MaxInstances)
- } else if xml.maybe_open(CAL_URN, "max-attendees-per-instance").await?.is_some() {
+ } else if xml
+ .maybe_open(CAL_URN, "max-attendees-per-instance")
+ .await?
+ .is_some()
+ {
xml.close().await?;
Ok(Self::MaxAttendeesPerInstance)
} else if xml.maybe_open(CAL_URN, "valid-filter").await?.is_some() {
@@ -167,7 +206,11 @@ impl QRead<Violation> for Violation {
}
xml.close().await?;
Ok(Self::SupportedFilter { comp, prop, param })
- } else if xml.maybe_open(CAL_URN, "number-of-matches-within-limits").await?.is_some() {
+ } else if xml
+ .maybe_open(CAL_URN, "number-of-matches-within-limits")
+ .await?
+ .is_some()
+ {
xml.close().await?;
Ok(Self::NumberOfMatchesWithinLimits)
} else {
@@ -178,72 +221,112 @@ impl QRead<Violation> for Violation {
impl QRead<Property> for Property {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
- if xml.maybe_open_start(CAL_URN, "calendar-home-set").await?.is_some() {
+ if xml
+ .maybe_open_start(CAL_URN, "calendar-home-set")
+ .await?
+ .is_some()
+ {
let href = xml.find().await?;
xml.close().await?;
- return Ok(Property::CalendarHomeSet(href))
+ return Ok(Property::CalendarHomeSet(href));
}
- if xml.maybe_open_start(CAL_URN, "calendar-description").await?.is_some() {
+ if xml
+ .maybe_open_start(CAL_URN, "calendar-description")
+ .await?
+ .is_some()
+ {
let lang = xml.prev_attr("xml:lang");
let text = xml.tag_string().await?;
xml.close().await?;
- return Ok(Property::CalendarDescription { lang, text })
+ return Ok(Property::CalendarDescription { lang, text });
}
- if xml.maybe_open_start(CAL_URN, "calendar-timezone").await?.is_some() {
+ if xml
+ .maybe_open_start(CAL_URN, "calendar-timezone")
+ .await?
+ .is_some()
+ {
let tz = xml.tag_string().await?;
xml.close().await?;
- return Ok(Property::CalendarTimezone(tz))
+ return Ok(Property::CalendarTimezone(tz));
}
- if xml.maybe_open_start(CAL_URN, "supported-calendar-component-set").await?.is_some() {
+ if xml
+ .maybe_open_start(CAL_URN, "supported-calendar-component-set")
+ .await?
+ .is_some()
+ {
let comp = xml.collect().await?;
xml.close().await?;
- return Ok(Property::SupportedCalendarComponentSet(comp))
+ return Ok(Property::SupportedCalendarComponentSet(comp));
}
- if xml.maybe_open_start(CAL_URN, "supported-calendar-data").await?.is_some() {
+ if xml
+ .maybe_open_start(CAL_URN, "supported-calendar-data")
+ .await?
+ .is_some()
+ {
let mime = xml.collect().await?;
xml.close().await?;
- return Ok(Property::SupportedCalendarData(mime))
+ return Ok(Property::SupportedCalendarData(mime));
}
- if xml.maybe_open_start(CAL_URN, "max-resource-size").await?.is_some() {
+ if xml
+ .maybe_open_start(CAL_URN, "max-resource-size")
+ .await?
+ .is_some()
+ {
let sz = xml.tag_string().await?.parse::<u64>()?;
xml.close().await?;
- return Ok(Property::MaxResourceSize(sz))
+ return Ok(Property::MaxResourceSize(sz));
}
- if xml.maybe_open_start(CAL_URN, "max-date-time").await?.is_some() {
+ if xml
+ .maybe_open_start(CAL_URN, "max-date-time")
+ .await?
+ .is_some()
+ {
let dtstr = xml.tag_string().await?;
let dt = NaiveDateTime::parse_from_str(dtstr.as_str(), ICAL_DATETIME_FMT)?.and_utc();
xml.close().await?;
- return Ok(Property::MaxDateTime(dt))
+ return Ok(Property::MaxDateTime(dt));
}
- if xml.maybe_open_start(CAL_URN, "max-instances").await?.is_some() {
+ if xml
+ .maybe_open_start(CAL_URN, "max-instances")
+ .await?
+ .is_some()
+ {
let sz = xml.tag_string().await?.parse::<u64>()?;
xml.close().await?;
- return Ok(Property::MaxInstances(sz))
+ return Ok(Property::MaxInstances(sz));
}
- if xml.maybe_open_start(CAL_URN, "max-attendees-per-instance").await?.is_some() {
+ if xml
+ .maybe_open_start(CAL_URN, "max-attendees-per-instance")
+ .await?
+ .is_some()
+ {
let sz = xml.tag_string().await?.parse::<u64>()?;
xml.close().await?;
- return Ok(Property::MaxAttendeesPerInstance(sz))
+ return Ok(Property::MaxAttendeesPerInstance(sz));
}
- if xml.maybe_open_start(CAL_URN, "supported-collation-set").await?.is_some() {
+ if xml
+ .maybe_open_start(CAL_URN, "supported-collation-set")
+ .await?
+ .is_some()
+ {
let cols = xml.collect().await?;
xml.close().await?;
- return Ok(Property::SupportedCollationSet(cols))
+ return Ok(Property::SupportedCollationSet(cols));
}
let mut dirty = false;
let mut caldata: Option<CalendarDataPayload> = None;
xml.maybe_read(&mut caldata, &mut dirty).await?;
if let Some(cal) = caldata {
- return Ok(Property::CalendarData(cal))
+ return Ok(Property::CalendarData(cal));
}
Err(ParsingError::Recoverable)
@@ -252,54 +335,88 @@ impl QRead<Property> for Property {
impl QRead<PropertyRequest> for PropertyRequest {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
- if xml.maybe_open(CAL_URN, "calendar-home-set").await?.is_some() {
+ if xml
+ .maybe_open(CAL_URN, "calendar-home-set")
+ .await?
+ .is_some()
+ {
xml.close().await?;
- return Ok(Self::CalendarHomeSet)
- }
- if xml.maybe_open(CAL_URN, "calendar-description").await?.is_some() {
+ return Ok(Self::CalendarHomeSet);
+ }
+ if xml
+ .maybe_open(CAL_URN, "calendar-description")
+ .await?
+ .is_some()
+ {
xml.close().await?;
- return Ok(Self::CalendarDescription)
- }
- if xml.maybe_open(CAL_URN, "calendar-timezone").await?.is_some() {
+ return Ok(Self::CalendarDescription);
+ }
+ if xml
+ .maybe_open(CAL_URN, "calendar-timezone")
+ .await?
+ .is_some()
+ {
xml.close().await?;
- return Ok(Self::CalendarTimezone)
+ return Ok(Self::CalendarTimezone);
}
- if xml.maybe_open(CAL_URN, "supported-calendar-component-set").await?.is_some() {
+ if xml
+ .maybe_open(CAL_URN, "supported-calendar-component-set")
+ .await?
+ .is_some()
+ {
xml.close().await?;
- return Ok(Self::SupportedCalendarComponentSet)
+ return Ok(Self::SupportedCalendarComponentSet);
}
- if xml.maybe_open(CAL_URN, "supported-calendar-data").await?.is_some() {
+ if xml
+ .maybe_open(CAL_URN, "supported-calendar-data")
+ .await?
+ .is_some()
+ {
xml.close().await?;
- return Ok(Self::SupportedCalendarData)
+ return Ok(Self::SupportedCalendarData);
}
- if xml.maybe_open(CAL_URN, "max-resource-size").await?.is_some() {
+ if xml
+ .maybe_open(CAL_URN, "max-resource-size")
+ .await?
+ .is_some()
+ {
xml.close().await?;
- return Ok(Self::MaxResourceSize)
+ return Ok(Self::MaxResourceSize);
}
if xml.maybe_open(CAL_URN, "min-date-time").await?.is_some() {
xml.close().await?;
- return Ok(Self::MinDateTime)
+ return Ok(Self::MinDateTime);
}
if xml.maybe_open(CAL_URN, "max-date-time").await?.is_some() {
xml.close().await?;
- return Ok(Self::MaxDateTime)
+ return Ok(Self::MaxDateTime);
}
if xml.maybe_open(CAL_URN, "max-instances").await?.is_some() {
xml.close().await?;
- return Ok(Self::MaxInstances)
+ return Ok(Self::MaxInstances);
}
- if xml.maybe_open(CAL_URN, "max-attendees-per-instance").await?.is_some() {
+ if xml
+ .maybe_open(CAL_URN, "max-attendees-per-instance")
+ .await?
+ .is_some()
+ {
xml.close().await?;
- return Ok(Self::MaxAttendeesPerInstance)
+ return Ok(Self::MaxAttendeesPerInstance);
}
- if xml.maybe_open(CAL_URN, "supported-collation-set").await?.is_some() {
+ if xml
+ .maybe_open(CAL_URN, "supported-collation-set")
+ .await?
+ .is_some()
+ {
xml.close().await?;
- return Ok(Self::SupportedCollationSet)
+ return Ok(Self::SupportedCollationSet);
}
let mut dirty = false;
let mut m_cdr = None;
xml.maybe_read(&mut m_cdr, &mut dirty).await?;
- m_cdr.ok_or(ParsingError::Recoverable).map(Self::CalendarData)
+ m_cdr
+ .ok_or(ParsingError::Recoverable)
+ .map(Self::CalendarData)
}
}
@@ -307,7 +424,7 @@ impl QRead<ResourceType> for ResourceType {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
if xml.maybe_open(CAL_URN, "calendar").await?.is_some() {
xml.close().await?;
- return Ok(Self::Calendar)
+ return Ok(Self::Calendar);
}
Err(ParsingError::Recoverable)
}
@@ -338,7 +455,10 @@ impl QRead<CalendarDataSupport> for CalendarDataSupport {
let ct = xml.prev_attr("content-type");
let vs = xml.prev_attr("version");
match (ct, vs) {
- (Some(content_type), Some(version)) => Ok(Self { content_type, version }),
+ (Some(content_type), Some(version)) => Ok(Self {
+ content_type,
+ version,
+ }),
_ => Err(ParsingError::Recoverable),
}
}
@@ -351,10 +471,14 @@ impl QRead<CalendarDataRequest> for CalendarDataRequest {
let (mut comp, mut recurrence, mut limit_freebusy_set) = (None, None, None);
if !xml.parent_has_child() {
- return Ok(Self { mime, comp, recurrence, limit_freebusy_set })
+ return Ok(Self {
+ mime,
+ comp,
+ recurrence,
+ limit_freebusy_set,
+ });
}
-
loop {
let mut dirty = false;
xml.maybe_read(&mut comp, &mut dirty).await?;
@@ -367,11 +491,15 @@ impl QRead<CalendarDataRequest> for CalendarDataRequest {
_ => xml.skip().await?,
};
}
-
}
xml.close().await?;
- Ok(Self { mime, comp, recurrence, limit_freebusy_set })
+ Ok(Self {
+ mime,
+ comp,
+ recurrence,
+ limit_freebusy_set,
+ })
}
}
@@ -389,17 +517,25 @@ impl QRead<Comp> for Comp {
let (mut prop_kind, mut comp_kind) = (None, None);
let bs = xml.open(CAL_URN, "comp").await?;
- let name = Component::new(xml.prev_attr("name").ok_or(ParsingError::MissingAttribute)?);
+ let name = Component::new(
+ xml.prev_attr("name")
+ .ok_or(ParsingError::MissingAttribute)?,
+ );
// Return early if it's an empty tag
if matches!(bs, Event::Empty(_)) {
xml.close().await?;
- return Ok(Self { name, prop_kind, comp_kind })
+ return Ok(Self {
+ name,
+ prop_kind,
+ comp_kind,
+ });
}
loop {
- let mut dirty = false;
- let (mut tmp_prop_kind, mut tmp_comp_kind): (Option<PropKind>, Option<CompKind>) = (None, None);
+ let mut dirty = false;
+ let (mut tmp_prop_kind, mut tmp_comp_kind): (Option<PropKind>, Option<CompKind>) =
+ (None, None);
xml.maybe_read(&mut tmp_prop_kind, &mut dirty).await?;
Box::pin(xml.maybe_read(&mut tmp_comp_kind, &mut dirty)).await?;
@@ -408,35 +544,41 @@ impl QRead<Comp> for Comp {
// Merge
match (tmp_prop_kind, &mut prop_kind) {
(Some(PropKind::Prop(mut a)), Some(PropKind::Prop(ref mut b))) => b.append(&mut a),
- (Some(PropKind::AllProp), v) => *v = Some(PropKind::AllProp),
+ (Some(PropKind::AllProp), v) => *v = Some(PropKind::AllProp),
(Some(x), b) => *b = Some(x),
(None, _) => (),
};
match (tmp_comp_kind, &mut comp_kind) {
(Some(CompKind::Comp(mut a)), Some(CompKind::Comp(ref mut b))) => b.append(&mut a),
- (Some(CompKind::AllComp), v) => *v = Some(CompKind::AllComp),
+ (Some(CompKind::AllComp), v) => *v = Some(CompKind::AllComp),
(Some(a), b) => *b = Some(a),
(None, _) => (),
};
-
if !dirty {
match xml.peek() {
Event::End(_) => break,
_ => xml.skip().await?,
};
}
- };
+ }
xml.close().await?;
- Ok(Self { name, prop_kind, comp_kind })
+ Ok(Self {
+ name,
+ prop_kind,
+ comp_kind,
+ })
}
}
impl QRead<CompSupport> for CompSupport {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
xml.open(CAL_URN, "comp").await?;
- let inner = Component::new(xml.prev_attr("name").ok_or(ParsingError::MissingAttribute)?);
+ let inner = Component::new(
+ xml.prev_attr("name")
+ .ok_or(ParsingError::MissingAttribute)?,
+ );
xml.close().await?;
Ok(Self(inner))
}
@@ -450,18 +592,18 @@ impl QRead<CompKind> for CompKind {
if xml.maybe_open(CAL_URN, "allcomp").await?.is_some() {
xml.close().await?;
- return Ok(CompKind::AllComp)
+ return Ok(CompKind::AllComp);
}
xml.maybe_push(&mut comp, &mut dirty).await?;
if !dirty {
- break
+ break;
}
}
match &comp[..] {
- [] => Err(ParsingError::Recoverable),
- _ => Ok(CompKind::Comp(comp)),
+ [] => Err(ParsingError::Recoverable),
+ _ => Ok(CompKind::Comp(comp)),
}
}
}
@@ -474,13 +616,13 @@ impl QRead<PropKind> for PropKind {
if xml.maybe_open(CAL_URN, "allprop").await?.is_some() {
xml.close().await?;
- return Ok(PropKind::AllProp)
+ return Ok(PropKind::AllProp);
}
xml.maybe_push(&mut prop, &mut dirty).await?;
if !dirty {
- break
+ break;
}
}
@@ -497,7 +639,9 @@ impl QRead<RecurrenceModifier> for RecurrenceModifier {
Err(ParsingError::Recoverable) => (),
otherwise => return otherwise.map(RecurrenceModifier::Expand),
}
- LimitRecurrenceSet::qread(xml).await.map(RecurrenceModifier::LimitRecurrenceSet)
+ LimitRecurrenceSet::qread(xml)
+ .await
+ .map(RecurrenceModifier::LimitRecurrenceSet)
}
}
@@ -508,11 +652,11 @@ impl QRead<Expand> for Expand {
(Some(start), Some(end)) => (start, end),
_ => return Err(ParsingError::MissingAttribute),
};
-
+
let start = NaiveDateTime::parse_from_str(rstart.as_str(), ICAL_DATETIME_FMT)?.and_utc();
let end = NaiveDateTime::parse_from_str(rend.as_str(), ICAL_DATETIME_FMT)?.and_utc();
if start > end {
- return Err(ParsingError::InvalidValue)
+ return Err(ParsingError::InvalidValue);
}
xml.close().await?;
@@ -527,11 +671,11 @@ impl QRead<LimitRecurrenceSet> for LimitRecurrenceSet {
(Some(start), Some(end)) => (start, end),
_ => return Err(ParsingError::MissingAttribute),
};
-
+
let start = NaiveDateTime::parse_from_str(rstart.as_str(), ICAL_DATETIME_FMT)?.and_utc();
let end = NaiveDateTime::parse_from_str(rend.as_str(), ICAL_DATETIME_FMT)?.and_utc();
if start > end {
- return Err(ParsingError::InvalidValue)
+ return Err(ParsingError::InvalidValue);
}
xml.close().await?;
@@ -546,11 +690,11 @@ impl QRead<LimitFreebusySet> for LimitFreebusySet {
(Some(start), Some(end)) => (start, end),
_ => return Err(ParsingError::MissingAttribute),
};
-
+
let start = NaiveDateTime::parse_from_str(rstart.as_str(), ICAL_DATETIME_FMT)?.and_utc();
let end = NaiveDateTime::parse_from_str(rend.as_str(), ICAL_DATETIME_FMT)?.and_utc();
if start > end {
- return Err(ParsingError::InvalidValue)
+ return Err(ParsingError::InvalidValue);
}
xml.close().await?;
@@ -563,20 +707,21 @@ impl<E: dav::Extension> QRead<CalendarSelector<E>> for CalendarSelector<E> {
// allprop
if let Some(_) = xml.maybe_open(DAV_URN, "allprop").await? {
xml.close().await?;
- return Ok(Self::AllProp)
+ return Ok(Self::AllProp);
}
// propname
if let Some(_) = xml.maybe_open(DAV_URN, "propname").await? {
xml.close().await?;
- return Ok(Self::PropName)
+ return Ok(Self::PropName);
}
// prop
let (mut maybe_prop, mut dirty) = (None, false);
- xml.maybe_read::<dav::PropName<E>>(&mut maybe_prop, &mut dirty).await?;
+ xml.maybe_read::<dav::PropName<E>>(&mut maybe_prop, &mut dirty)
+ .await?;
if let Some(prop) = maybe_prop {
- return Ok(Self::Prop(prop))
+ return Ok(Self::Prop(prop));
}
Err(ParsingError::Recoverable)
@@ -586,10 +731,16 @@ impl<E: dav::Extension> QRead<CalendarSelector<E>> for CalendarSelector<E> {
impl QRead<CompFilter> for CompFilter {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
xml.open(CAL_URN, "comp-filter").await?;
- let name = Component::new(xml.prev_attr("name").ok_or(ParsingError::MissingAttribute)?);
+ let name = Component::new(
+ xml.prev_attr("name")
+ .ok_or(ParsingError::MissingAttribute)?,
+ );
let additional_rules = Box::pin(xml.maybe_find()).await?;
xml.close().await?;
- Ok(Self { name, additional_rules })
+ Ok(Self {
+ name,
+ additional_rules,
+ })
}
}
@@ -604,7 +755,7 @@ impl QRead<CompFilterRules> for CompFilterRules {
if xml.maybe_open(CAL_URN, "is-not-defined").await?.is_some() {
xml.close().await?;
- return Ok(Self::IsNotDefined)
+ return Ok(Self::IsNotDefined);
}
xml.maybe_read(&mut time_range, &mut dirty).await?;
@@ -621,7 +772,11 @@ impl QRead<CompFilterRules> for CompFilterRules {
match (&time_range, &prop_filter[..], &comp_filter[..]) {
(None, [], []) => Err(ParsingError::Recoverable),
- _ => Ok(Self::Matches(CompFilterMatch { time_range, prop_filter, comp_filter })),
+ _ => Ok(Self::Matches(CompFilterMatch {
+ time_range,
+ prop_filter,
+ comp_filter,
+ })),
}
}
}
@@ -635,10 +790,16 @@ impl QRead<CompFilterMatch> for CompFilterMatch {
impl QRead<PropFilter> for PropFilter {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
xml.open(CAL_URN, "prop-filter").await?;
- let name = ComponentProperty(xml.prev_attr("name").ok_or(ParsingError::MissingAttribute)?);
+ let name = ComponentProperty(
+ xml.prev_attr("name")
+ .ok_or(ParsingError::MissingAttribute)?,
+ );
let additional_rules = xml.maybe_find().await?;
xml.close().await?;
- Ok(Self { name, additional_rules })
+ Ok(Self {
+ name,
+ additional_rules,
+ })
}
}
@@ -653,7 +814,7 @@ impl QRead<PropFilterRules> for PropFilterRules {
if xml.maybe_open(CAL_URN, "is-not-defined").await?.is_some() {
xml.close().await?;
- return Ok(Self::IsNotDefined)
+ return Ok(Self::IsNotDefined);
}
xml.maybe_read(&mut time_range, &mut dirty).await?;
@@ -670,7 +831,11 @@ impl QRead<PropFilterRules> for PropFilterRules {
match (&time_range, &time_or_text, &param_filter[..]) {
(None, None, []) => Err(ParsingError::Recoverable),
- _ => Ok(PropFilterRules::Match(PropFilterMatch { time_range, time_or_text, param_filter })),
+ _ => Ok(PropFilterRules::Match(PropFilterMatch {
+ time_range,
+ time_or_text,
+ param_filter,
+ })),
}
}
}
@@ -684,10 +849,16 @@ impl QRead<PropFilterMatch> for PropFilterMatch {
impl QRead<ParamFilter> for ParamFilter {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
xml.open(CAL_URN, "param-filter").await?;
- let name = PropertyParameter(xml.prev_attr("name").ok_or(ParsingError::MissingAttribute)?);
+ let name = PropertyParameter(
+ xml.prev_attr("name")
+ .ok_or(ParsingError::MissingAttribute)?,
+ );
let additional_rules = xml.maybe_find().await?;
xml.close().await?;
- Ok(Self { name, additional_rules })
+ Ok(Self {
+ name,
+ additional_rules,
+ })
}
}
@@ -708,7 +879,11 @@ impl QRead<TextMatch> for TextMatch {
let negate_condition = xml.prev_attr("negate-condition").map(|v| v == "yes");
let text = xml.tag_string().await?;
xml.close().await?;
- Ok(Self { collation, negate_condition, text })
+ Ok(Self {
+ collation,
+ negate_condition,
+ text,
+ })
}
}
@@ -716,7 +891,7 @@ impl QRead<ParamFilterMatch> for ParamFilterMatch {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
if xml.maybe_open(CAL_URN, "is-not-defined").await?.is_some() {
xml.close().await?;
- return Ok(Self::IsNotDefined)
+ return Ok(Self::IsNotDefined);
}
TextMatch::qread(xml).await.map(Self::Match)
}
@@ -745,11 +920,15 @@ impl QRead<TimeRange> for TimeRange {
xml.open(CAL_URN, "time-range").await?;
let start = match xml.prev_attr("start") {
- Some(r) => Some(NaiveDateTime::parse_from_str(r.as_str(), ICAL_DATETIME_FMT)?.and_utc()),
+ Some(r) => {
+ Some(NaiveDateTime::parse_from_str(r.as_str(), ICAL_DATETIME_FMT)?.and_utc())
+ }
_ => None,
};
let end = match xml.prev_attr("end") {
- Some(r) => Some(NaiveDateTime::parse_from_str(r.as_str(), ICAL_DATETIME_FMT)?.and_utc()),
+ Some(r) => {
+ Some(NaiveDateTime::parse_from_str(r.as_str(), ICAL_DATETIME_FMT)?.and_utc())
+ }
_ => None,
};
@@ -758,10 +937,10 @@ impl QRead<TimeRange> for TimeRange {
match (start, end) {
(Some(start), Some(end)) => {
if start > end {
- return Err(ParsingError::InvalidValue)
+ return Err(ParsingError::InvalidValue);
}
Ok(TimeRange::FullRange(start, end))
- },
+ }
(Some(start), None) => Ok(TimeRange::OnlyStart(start)),
(None, Some(end)) => Ok(TimeRange::OnlyEnd(end)),
(None, None) => Err(ParsingError::MissingAttribute),
@@ -771,8 +950,11 @@ impl QRead<TimeRange> for TimeRange {
impl QRead<CalProp> for CalProp {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
- xml.open(CAL_URN, "prop").await?;
- let name = ComponentProperty(xml.prev_attr("name").ok_or(ParsingError::MissingAttribute)?);
+ xml.open(CAL_URN, "prop").await?;
+ let name = ComponentProperty(
+ xml.prev_attr("name")
+ .ok_or(ParsingError::MissingAttribute)?,
+ );
let novalue = xml.prev_attr("novalue").map(|v| v == "yes");
xml.close().await?;
Ok(Self { name, novalue })
@@ -782,21 +964,23 @@ impl QRead<CalProp> for CalProp {
#[cfg(test)]
mod tests {
use super::*;
- use chrono::{Utc, TimeZone};
use crate::realization::Calendar;
use crate::xml::Node;
+ use chrono::{TimeZone, Utc};
//use quick_reader::NsReader;
async fn deserialize<T: Node<T>>(src: &str) -> T {
- let mut rdr = Reader::new(quick_xml::NsReader::from_reader(src.as_bytes())).await.unwrap();
+ let mut rdr = Reader::new(quick_xml::NsReader::from_reader(src.as_bytes()))
+ .await
+ .unwrap();
rdr.find().await.unwrap()
}
#[tokio::test]
async fn basic_mkcalendar() {
- let expected = MkCalendar(dav::Set(dav::PropValue(vec![
- dav::Property::DisplayName("Lisa's Events".into()),
- ])));
+ let expected = MkCalendar(dav::Set(dav::PropValue(vec![dav::Property::DisplayName(
+ "Lisa's Events".into(),
+ )])));
let src = r#"
<?xml version="1.0" encoding="utf-8" ?>
@@ -856,61 +1040,89 @@ END:VCALENDAR]]></C:calendar-timezone>
let expected = CalendarQuery {
selector: Some(CalendarSelector::Prop(dav::PropName(vec![
dav::PropertyRequest::GetEtag,
- dav::PropertyRequest::Extension(PropertyRequest::CalendarData(CalendarDataRequest {
- mime: None,
- comp: Some(Comp {
- name: Component::VCalendar,
- prop_kind: Some(PropKind::Prop(vec![
- CalProp {
+ dav::PropertyRequest::Extension(PropertyRequest::CalendarData(
+ CalendarDataRequest {
+ mime: None,
+ comp: Some(Comp {
+ name: Component::VCalendar,
+ prop_kind: Some(PropKind::Prop(vec![CalProp {
name: ComponentProperty("VERSION".into()),
novalue: None,
- }
- ])),
- comp_kind: Some(CompKind::Comp(vec![
- Comp {
- name: Component::VEvent,
- prop_kind: Some(PropKind::Prop(vec![
- CalProp { name: ComponentProperty("SUMMARY".into()), novalue: None },
- CalProp { name: ComponentProperty("UID".into()), novalue: None },
- CalProp { name: ComponentProperty("DTSTART".into()), novalue: None },
- CalProp { name: ComponentProperty("DTEND".into()), novalue: None },
- CalProp { name: ComponentProperty("DURATION".into()), novalue: None },
- CalProp { name: ComponentProperty("RRULE".into()), novalue: None },
- CalProp { name: ComponentProperty("RDATE".into()), novalue: None },
- CalProp { name: ComponentProperty("EXRULE".into()), novalue: None },
- CalProp { name: ComponentProperty("EXDATE".into()), novalue: None },
- CalProp { name: ComponentProperty("RECURRENCE-ID".into()), novalue: None },
- ])),
- comp_kind: None,
- },
- Comp {
- name: Component::VTimeZone,
- prop_kind: None,
- comp_kind: None,
- }
- ])),
- }),
- recurrence: None,
- limit_freebusy_set: None,
- })),
+ }])),
+ comp_kind: Some(CompKind::Comp(vec![
+ Comp {
+ name: Component::VEvent,
+ prop_kind: Some(PropKind::Prop(vec![
+ CalProp {
+ name: ComponentProperty("SUMMARY".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("UID".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("DTSTART".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("DTEND".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("DURATION".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("RRULE".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("RDATE".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("EXRULE".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("EXDATE".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("RECURRENCE-ID".into()),
+ novalue: None,
+ },
+ ])),
+ comp_kind: None,
+ },
+ Comp {
+ name: Component::VTimeZone,
+ prop_kind: None,
+ comp_kind: None,
+ },
+ ])),
+ }),
+ recurrence: None,
+ limit_freebusy_set: None,
+ },
+ )),
]))),
filter: Filter(CompFilter {
name: Component::VCalendar,
additional_rules: Some(CompFilterRules::Matches(CompFilterMatch {
prop_filter: vec![],
- comp_filter: vec![
- CompFilter {
- name: Component::VEvent,
- additional_rules: Some(CompFilterRules::Matches(CompFilterMatch {
- prop_filter: vec![],
- comp_filter: vec![],
- time_range: Some(TimeRange::FullRange(
- Utc.with_ymd_and_hms(2006, 1, 4, 0, 0, 0).unwrap(),
- Utc.with_ymd_and_hms(2006, 1, 5, 0, 0, 0).unwrap(),
- )),
- })),
- },
- ],
+ comp_filter: vec![CompFilter {
+ name: Component::VEvent,
+ additional_rules: Some(CompFilterRules::Matches(CompFilterMatch {
+ prop_filter: vec![],
+ comp_filter: vec![],
+ time_range: Some(TimeRange::FullRange(
+ Utc.with_ymd_and_hms(2006, 1, 4, 0, 0, 0).unwrap(),
+ Utc.with_ymd_and_hms(2006, 1, 5, 0, 0, 0).unwrap(),
+ )),
+ })),
+ }],
time_range: None,
})),
}),
@@ -958,26 +1170,28 @@ END:VCALENDAR]]></C:calendar-timezone>
}
#[tokio::test]
- async fn rfc_calendar_query_res() {
+ async fn rfc_calendar_query_res() {
let expected = dav::Multistatus::<Calendar> {
responses: vec![
dav::Response {
status_or_propstat: dav::StatusOrPropstat::PropStat(
dav::Href("http://cal.example.com/bernard/work/abcd2.ics".into()),
- vec![
- dav::PropStat {
- prop: dav::AnyProp(vec![
- dav::AnyProperty::Value(dav::Property::GetEtag("\"fffff-abcd2\"".into())),
- dav::AnyProperty::Value(dav::Property::Extension(Property::CalendarData(CalendarDataPayload {
+ vec![dav::PropStat {
+ prop: dav::AnyProp(vec![
+ dav::AnyProperty::Value(dav::Property::GetEtag(
+ "\"fffff-abcd2\"".into(),
+ )),
+ dav::AnyProperty::Value(dav::Property::Extension(
+ Property::CalendarData(CalendarDataPayload {
mime: None,
payload: "BEGIN:VCALENDAR".into(),
- }))),
- ]),
- status: dav::Status(http::status::StatusCode::OK),
- error: None,
- responsedescription: None,
- },
- ],
+ }),
+ )),
+ ]),
+ status: dav::Status(http::status::StatusCode::OK),
+ error: None,
+ responsedescription: None,
+ }],
),
error: None,
location: None,
@@ -986,20 +1200,22 @@ END:VCALENDAR]]></C:calendar-timezone>
dav::Response {
status_or_propstat: dav::StatusOrPropstat::PropStat(
dav::Href("http://cal.example.com/bernard/work/abcd3.ics".into()),
- vec![
- dav::PropStat {
- prop: dav::AnyProp(vec![
- dav::AnyProperty::Value(dav::Property::GetEtag("\"fffff-abcd3\"".into())),
- dav::AnyProperty::Value(dav::Property::Extension(Property::CalendarData(CalendarDataPayload {
+ vec![dav::PropStat {
+ prop: dav::AnyProp(vec![
+ dav::AnyProperty::Value(dav::Property::GetEtag(
+ "\"fffff-abcd3\"".into(),
+ )),
+ dav::AnyProperty::Value(dav::Property::Extension(
+ Property::CalendarData(CalendarDataPayload {
mime: None,
payload: "BEGIN:VCALENDAR".into(),
- }))),
- ]),
- status: dav::Status(http::status::StatusCode::OK),
- error: None,
- responsedescription: None,
- },
- ],
+ }),
+ )),
+ ]),
+ status: dav::Status(http::status::StatusCode::OK),
+ error: None,
+ responsedescription: None,
+ }],
),
error: None,
location: None,
@@ -1039,36 +1255,38 @@ END:VCALENDAR]]></C:calendar-timezone>
}
#[tokio::test]
- async fn rfc_recurring_evt() {
+ async fn rfc_recurring_evt() {
let expected = CalendarQuery::<Calendar> {
selector: Some(CalendarSelector::Prop(dav::PropName(vec![
- dav::PropertyRequest::Extension(PropertyRequest::CalendarData(CalendarDataRequest{
- mime: None,
- comp: None,
- recurrence: Some(RecurrenceModifier::LimitRecurrenceSet(LimitRecurrenceSet (
- Utc.with_ymd_and_hms(2006, 1, 3, 0, 0, 0).unwrap(),
- Utc.with_ymd_and_hms(2006, 1, 5, 0, 0, 0).unwrap(),
- ))),
- limit_freebusy_set: None,
- })),
+ dav::PropertyRequest::Extension(PropertyRequest::CalendarData(
+ CalendarDataRequest {
+ mime: None,
+ comp: None,
+ recurrence: Some(RecurrenceModifier::LimitRecurrenceSet(
+ LimitRecurrenceSet(
+ Utc.with_ymd_and_hms(2006, 1, 3, 0, 0, 0).unwrap(),
+ Utc.with_ymd_and_hms(2006, 1, 5, 0, 0, 0).unwrap(),
+ ),
+ )),
+ limit_freebusy_set: None,
+ },
+ )),
]))),
filter: Filter(CompFilter {
name: Component::VCalendar,
additional_rules: Some(CompFilterRules::Matches(CompFilterMatch {
prop_filter: vec![],
- comp_filter: vec![
- CompFilter {
- name: Component::VEvent,
- additional_rules: Some(CompFilterRules::Matches(CompFilterMatch {
- prop_filter: vec![],
- comp_filter: vec![],
- time_range: Some(TimeRange::FullRange(
- Utc.with_ymd_and_hms(2006, 1, 3, 0, 0, 0).unwrap(),
- Utc.with_ymd_and_hms(2006, 1, 5, 0, 0, 0).unwrap(),
- )),
- })),
- },
- ],
+ comp_filter: vec![CompFilter {
+ name: Component::VEvent,
+ additional_rules: Some(CompFilterRules::Matches(CompFilterMatch {
+ prop_filter: vec![],
+ comp_filter: vec![],
+ time_range: Some(TimeRange::FullRange(
+ Utc.with_ymd_and_hms(2006, 1, 3, 0, 0, 0).unwrap(),
+ Utc.with_ymd_and_hms(2006, 1, 5, 0, 0, 0).unwrap(),
+ )),
+ })),
+ }],
time_range: None,
})),
}),
@@ -1104,32 +1322,34 @@ END:VCALENDAR]]></C:calendar-timezone>
let expected = CalendarQuery::<Calendar> {
selector: Some(CalendarSelector::Prop(dav::PropName(vec![
dav::PropertyRequest::GetEtag,
- dav::PropertyRequest::Extension(PropertyRequest::CalendarData(CalendarDataRequest {
- mime: None,
- comp: None,
- recurrence: None,
- limit_freebusy_set: None,
- }))
+ dav::PropertyRequest::Extension(PropertyRequest::CalendarData(
+ CalendarDataRequest {
+ mime: None,
+ comp: None,
+ recurrence: None,
+ limit_freebusy_set: None,
+ },
+ )),
]))),
filter: Filter(CompFilter {
name: Component::VCalendar,
additional_rules: Some(CompFilterRules::Matches(CompFilterMatch {
time_range: None,
prop_filter: vec![],
- comp_filter: vec![
- CompFilter {
- name: Component::VTodo,
- additional_rules: Some(CompFilterRules::Matches(CompFilterMatch {
- time_range: None,
- comp_filter: vec![],
- prop_filter: vec![
- PropFilter {
- name: ComponentProperty("COMPLETED".into()),
- additional_rules: Some(PropFilterRules::IsNotDefined),
- },
- PropFilter {
- name: ComponentProperty("STATUS".into()),
- additional_rules: Some(PropFilterRules::Match(PropFilterMatch {
+ comp_filter: vec![CompFilter {
+ name: Component::VTodo,
+ additional_rules: Some(CompFilterRules::Matches(CompFilterMatch {
+ time_range: None,
+ comp_filter: vec![],
+ prop_filter: vec![
+ PropFilter {
+ name: ComponentProperty("COMPLETED".into()),
+ additional_rules: Some(PropFilterRules::IsNotDefined),
+ },
+ PropFilter {
+ name: ComponentProperty("STATUS".into()),
+ additional_rules: Some(PropFilterRules::Match(
+ PropFilterMatch {
time_range: None,
param_filter: vec![],
time_or_text: Some(TimeOrText::Text(TextMatch {
@@ -1137,12 +1357,12 @@ END:VCALENDAR]]></C:calendar-timezone>
negate_condition: Some(true),
text: "CANCELLED".into(),
})),
- })),
- },
- ],
- })),
- }
- ],
+ },
+ )),
+ },
+ ],
+ })),
+ }],
})),
}),
timezone: None,
@@ -1169,9 +1389,7 @@ END:VCALENDAR]]></C:calendar-timezone>
</C:filter>
</C:calendar-query>"#;
-
let got = deserialize::<CalendarQuery<Calendar>>(src).await;
assert_eq!(got, expected)
-
}
}
diff --git a/aero-dav/src/calencoder.rs b/aero-dav/src/calencoder.rs
index d324c7f..06cafd4 100644
--- a/aero-dav/src/calencoder.rs
+++ b/aero-dav/src/calencoder.rs
@@ -1,10 +1,9 @@
+use quick_xml::events::{BytesText, Event};
use quick_xml::Error as QError;
-use quick_xml::events::{Event, BytesText};
use super::caltypes::*;
-use super::xml::{Node, QWrite, IWrite, Writer};
use super::types::Extension;
-
+use super::xml::{IWrite, Node, QWrite, Writer};
// ==================== Calendar Types Serialization =========================
@@ -54,7 +53,7 @@ impl<E: Extension> QWrite for CalendarQuery<E> {
selector.qwrite(xml).await?;
}
self.filter.qwrite(xml).await?;
- if let Some(tz) = &self.timezone {
+ if let Some(tz) = &self.timezone {
tz.qwrite(xml).await?;
}
xml.q.write_event_async(Event::End(end)).await
@@ -106,8 +105,8 @@ impl QWrite for PropertyRequest {
Self::MinDateTime => atom("min-date-time").await,
Self::MaxDateTime => atom("max-date-time").await,
Self::MaxInstances => atom("max-instances").await,
- Self::MaxAttendeesPerInstance => atom("max-attendees-per-instance").await,
- Self::SupportedCollationSet => atom("supported-collation-set").await,
+ Self::MaxAttendeesPerInstance => atom("max-attendees-per-instance").await,
+ Self::SupportedCollationSet => atom("supported-collation-set").await,
Self::CalendarData(req) => req.qwrite(xml).await,
}
}
@@ -130,17 +129,21 @@ impl QWrite for Property {
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(text))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(text)))
+ .await?;
xml.q.write_event_async(Event::End(end)).await
- },
+ }
Self::CalendarTimezone(payload) => {
let start = xml.create_cal_element("calendar-timezone");
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(payload))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(payload)))
+ .await?;
xml.q.write_event_async(Event::End(end)).await
- },
+ }
Self::SupportedCalendarComponentSet(many_comp) => {
let start = xml.create_cal_element("supported-calendar-component-set");
let end = start.to_end();
@@ -150,7 +153,7 @@ impl QWrite for Property {
comp.qwrite(xml).await?;
}
xml.q.write_event_async(Event::End(end)).await
- },
+ }
Self::SupportedCalendarData(many_mime) => {
let start = xml.create_cal_element("supported-calendar-data");
let end = start.to_end();
@@ -160,49 +163,59 @@ impl QWrite for Property {
mime.qwrite(xml).await?;
}
xml.q.write_event_async(Event::End(end)).await
- },
+ }
Self::MaxResourceSize(bytes) => {
let start = xml.create_cal_element("max-resource-size");
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(bytes.to_string().as_str()))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(bytes.to_string().as_str())))
+ .await?;
xml.q.write_event_async(Event::End(end)).await
- },
+ }
Self::MinDateTime(dt) => {
let start = xml.create_cal_element("min-date-time");
let end = start.to_end();
let dtstr = format!("{}", dt.format(ICAL_DATETIME_FMT));
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(dtstr.as_str()))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(dtstr.as_str())))
+ .await?;
xml.q.write_event_async(Event::End(end)).await
- },
+ }
Self::MaxDateTime(dt) => {
let start = xml.create_cal_element("max-date-time");
let end = start.to_end();
let dtstr = format!("{}", dt.format(ICAL_DATETIME_FMT));
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(dtstr.as_str()))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(dtstr.as_str())))
+ .await?;
xml.q.write_event_async(Event::End(end)).await
- },
+ }
Self::MaxInstances(count) => {
let start = xml.create_cal_element("max-instances");
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(count.to_string().as_str()))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(count.to_string().as_str())))
+ .await?;
xml.q.write_event_async(Event::End(end)).await
- },
+ }
Self::MaxAttendeesPerInstance(count) => {
let start = xml.create_cal_element("max-attendees-per-instance");
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(count.to_string().as_str()))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(count.to_string().as_str())))
+ .await?;
xml.q.write_event_async(Event::End(end)).await
- },
+ }
Self::SupportedCollationSet(many_collations) => {
let start = xml.create_cal_element("supported-collation-set");
let end = start.to_end();
@@ -211,8 +224,8 @@ impl QWrite for Property {
for collation in many_collations.iter() {
collation.qwrite(xml).await?;
}
- xml.q.write_event_async(Event::End(end)).await
- },
+ xml.q.write_event_async(Event::End(end)).await
+ }
Self::CalendarData(inner) => inner.qwrite(xml).await,
}
}
@@ -225,7 +238,7 @@ impl QWrite for ResourceType {
Self::Calendar => {
let empty_tag = xml.create_cal_element("calendar");
xml.q.write_event_async(Event::Empty(empty_tag)).await
- },
+ }
}
}
}
@@ -245,7 +258,7 @@ impl QWrite for Violation {
Self::NeedPrivileges => {
let empty_tag = xml.create_dav_element("need-privileges");
xml.q.write_event_async(Event::Empty(empty_tag)).await
- },
+ }
// Regular CalDAV errors
Self::ResourceMustBeNull => atom("resource-must-be-null").await,
@@ -262,7 +275,7 @@ impl QWrite for Violation {
xml.q.write_event_async(Event::Start(start.clone())).await?;
href.qwrite(xml).await?;
xml.q.write_event_async(Event::End(end)).await
- },
+ }
Self::MaxResourceSize => atom("max-resource-size").await,
Self::MinDateTime => atom("min-date-time").await,
Self::MaxDateTime => atom("max-date-time").await,
@@ -284,13 +297,12 @@ impl QWrite for Violation {
param_item.qwrite(xml).await?;
}
xml.q.write_event_async(Event::End(end)).await
- },
+ }
Self::NumberOfMatchesWithinLimits => atom("number-of-matches-within-limits").await,
}
}
}
-
// ---------------------------- Inner XML ------------------------------------
impl QWrite for SupportedCollation {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
@@ -300,19 +312,20 @@ impl QWrite for SupportedCollation {
xml.q.write_event_async(Event::Start(start.clone())).await?;
self.0.qwrite(xml).await?;
xml.q.write_event_async(Event::End(end)).await
-
}
}
impl QWrite for Collation {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
let col = match self {
- Self::AsciiCaseMap => "i;ascii-casemap",
- Self::Octet => "i;octet",
- Self::Unknown(v) => v.as_str(),
+ Self::AsciiCaseMap => "i;ascii-casemap",
+ Self::Octet => "i;octet",
+ Self::Unknown(v) => v.as_str(),
};
- xml.q.write_event_async(Event::Text(BytesText::new(col))).await
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(col)))
+ .await
}
}
@@ -332,7 +345,9 @@ impl QWrite for CalendarDataPayload {
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(self.payload.as_str()))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(self.payload.as_str())))
+ .await?;
xml.q.write_event_async(Event::End(end)).await
}
}
@@ -347,7 +362,7 @@ impl QWrite for CalendarDataRequest {
// Empty tag
if self.comp.is_none() && self.recurrence.is_none() && self.limit_freebusy_set.is_none() {
- return xml.q.write_event_async(Event::Empty(start.clone())).await
+ return xml.q.write_event_async(Event::Empty(start.clone())).await;
}
let end = start.to_end();
@@ -392,7 +407,7 @@ impl QWrite for Comp {
comp_kind.qwrite(xml).await?;
}
xml.q.write_event_async(Event::End(end)).await
- },
+ }
}
}
}
@@ -411,7 +426,7 @@ impl QWrite for CompKind {
Self::AllComp => {
let empty_tag = xml.create_cal_element("allcomp");
xml.q.write_event_async(Event::Empty(empty_tag)).await
- },
+ }
Self::Comp(many_comp) => {
for comp in many_comp.iter() {
// Required: recursion in an async fn requires boxing
@@ -420,7 +435,10 @@ impl QWrite for CompKind {
// For more information about this error, try `rustc --explain E0391`.
// https://github.com/rust-lang/rust/issues/78649
#[inline(always)]
- fn recurse<'a>(comp: &'a Comp, xml: &'a mut Writer<impl IWrite>) -> futures::future::BoxFuture<'a, Result<(), QError>> {
+ fn recurse<'a>(
+ comp: &'a Comp,
+ xml: &'a mut Writer<impl IWrite>,
+ ) -> futures::future::BoxFuture<'a, Result<(), QError>> {
Box::pin(comp.qwrite(xml))
}
recurse(comp, xml).await?;
@@ -437,7 +455,7 @@ impl QWrite for PropKind {
Self::AllProp => {
let empty_tag = xml.create_cal_element("allprop");
xml.q.write_event_async(Event::Empty(empty_tag)).await
- },
+ }
Self::Prop(many_prop) => {
for prop in many_prop.iter() {
prop.qwrite(xml).await?;
@@ -473,8 +491,14 @@ impl QWrite for RecurrenceModifier {
impl QWrite for Expand {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
let mut empty = xml.create_cal_element("expand");
- empty.push_attribute(("start", format!("{}", self.0.format(ICAL_DATETIME_FMT)).as_str()));
- empty.push_attribute(("end", format!("{}", self.1.format(ICAL_DATETIME_FMT)).as_str()));
+ empty.push_attribute((
+ "start",
+ format!("{}", self.0.format(ICAL_DATETIME_FMT)).as_str(),
+ ));
+ empty.push_attribute((
+ "end",
+ format!("{}", self.1.format(ICAL_DATETIME_FMT)).as_str(),
+ ));
xml.q.write_event_async(Event::Empty(empty)).await
}
}
@@ -482,8 +506,14 @@ impl QWrite for Expand {
impl QWrite for LimitRecurrenceSet {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
let mut empty = xml.create_cal_element("limit-recurrence-set");
- empty.push_attribute(("start", format!("{}", self.0.format(ICAL_DATETIME_FMT)).as_str()));
- empty.push_attribute(("end", format!("{}", self.1.format(ICAL_DATETIME_FMT)).as_str()));
+ empty.push_attribute((
+ "start",
+ format!("{}", self.0.format(ICAL_DATETIME_FMT)).as_str(),
+ ));
+ empty.push_attribute((
+ "end",
+ format!("{}", self.1.format(ICAL_DATETIME_FMT)).as_str(),
+ ));
xml.q.write_event_async(Event::Empty(empty)).await
}
}
@@ -491,8 +521,14 @@ impl QWrite for LimitRecurrenceSet {
impl QWrite for LimitFreebusySet {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
let mut empty = xml.create_cal_element("limit-freebusy-set");
- empty.push_attribute(("start", format!("{}", self.0.format(ICAL_DATETIME_FMT)).as_str()));
- empty.push_attribute(("end", format!("{}", self.1.format(ICAL_DATETIME_FMT)).as_str()));
+ empty.push_attribute((
+ "start",
+ format!("{}", self.0.format(ICAL_DATETIME_FMT)).as_str(),
+ ));
+ empty.push_attribute((
+ "end",
+ format!("{}", self.1.format(ICAL_DATETIME_FMT)).as_str(),
+ ));
xml.q.write_event_async(Event::Empty(empty)).await
}
}
@@ -503,11 +539,11 @@ impl<E: Extension> QWrite for CalendarSelector<E> {
Self::AllProp => {
let empty_tag = xml.create_dav_element("allprop");
xml.q.write_event_async(Event::Empty(empty_tag)).await
- },
+ }
Self::PropName => {
let empty_tag = xml.create_dav_element("propname");
xml.q.write_event_async(Event::Empty(empty_tag)).await
- },
+ }
Self::Prop(prop) => prop.qwrite(xml).await,
}
}
@@ -534,10 +570,10 @@ impl QWrite for CompFilter {
impl QWrite for CompFilterRules {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
match self {
- Self::IsNotDefined => {
+ Self::IsNotDefined => {
let empty_tag = xml.create_dav_element("is-not-defined");
xml.q.write_event_async(Event::Empty(empty_tag)).await
- },
+ }
Self::Matches(cfm) => cfm.qwrite(xml).await,
}
}
@@ -559,7 +595,10 @@ impl QWrite for CompFilterMatch {
// For more information about this error, try `rustc --explain E0391`.
// https://github.com/rust-lang/rust/issues/78649
#[inline(always)]
- fn recurse<'a>(comp: &'a CompFilter, xml: &'a mut Writer<impl IWrite>) -> futures::future::BoxFuture<'a, Result<(), QError>> {
+ fn recurse<'a>(
+ comp: &'a CompFilter,
+ xml: &'a mut Writer<impl IWrite>,
+ ) -> futures::future::BoxFuture<'a, Result<(), QError>> {
Box::pin(comp.qwrite(xml))
}
recurse(comp_item, xml).await?;
@@ -591,7 +630,7 @@ impl QWrite for PropFilterRules {
Self::IsNotDefined => {
let empty_tag = xml.create_dav_element("is-not-defined");
xml.q.write_event_async(Event::Empty(empty_tag)).await
- },
+ }
Self::Match(prop_match) => prop_match.qwrite(xml).await,
}
}
@@ -635,7 +674,9 @@ impl QWrite for TextMatch {
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(self.text.as_str()))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(self.text.as_str())))
+ .await?;
xml.q.write_event_async(Event::End(end)).await
}
}
@@ -663,7 +704,7 @@ impl QWrite for ParamFilterMatch {
Self::IsNotDefined => {
let empty_tag = xml.create_dav_element("is-not-defined");
xml.q.write_event_async(Event::Empty(empty_tag)).await
- },
+ }
Self::Match(tm) => tm.qwrite(xml).await,
}
}
@@ -675,7 +716,9 @@ impl QWrite for TimeZone {
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(self.0.as_str()))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(self.0.as_str())))
+ .await?;
xml.q.write_event_async(Event::End(end)).await
}
}
@@ -695,11 +738,20 @@ impl QWrite for TimeRange {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
let mut empty = xml.create_cal_element("time-range");
match self {
- Self::OnlyStart(start) => empty.push_attribute(("start", format!("{}", start.format(ICAL_DATETIME_FMT)).as_str())),
- Self::OnlyEnd(end) => empty.push_attribute(("end", format!("{}", end.format(ICAL_DATETIME_FMT)).as_str())),
+ Self::OnlyStart(start) => empty.push_attribute((
+ "start",
+ format!("{}", start.format(ICAL_DATETIME_FMT)).as_str(),
+ )),
+ Self::OnlyEnd(end) => {
+ empty.push_attribute(("end", format!("{}", end.format(ICAL_DATETIME_FMT)).as_str()))
+ }
Self::FullRange(start, end) => {
- empty.push_attribute(("start", format!("{}", start.format(ICAL_DATETIME_FMT)).as_str()));
- empty.push_attribute(("end", format!("{}", end.format(ICAL_DATETIME_FMT)).as_str()));
+ empty.push_attribute((
+ "start",
+ format!("{}", start.format(ICAL_DATETIME_FMT)).as_str(),
+ ));
+ empty
+ .push_attribute(("end", format!("{}", end.format(ICAL_DATETIME_FMT)).as_str()));
}
}
xml.q.write_event_async(Event::Empty(empty)).await
@@ -709,16 +761,16 @@ impl QWrite for TimeRange {
#[cfg(test)]
mod tests {
use super::*;
- use crate::types as dav;
use crate::realization::Calendar;
+ use crate::types as dav;
+ use chrono::{TimeZone, Utc};
use tokio::io::AsyncWriteExt;
- use chrono::{Utc,TimeZone};
async fn serialize(elem: &impl QWrite) -> String {
let mut buffer = Vec::new();
let mut tokio_buffer = tokio::io::BufWriter::new(&mut buffer);
let q = quick_xml::writer::Writer::new_with_indent(&mut tokio_buffer, b' ', 4);
- let ns_to_apply = vec![
+ let ns_to_apply = vec![
("xmlns:D".into(), "DAV:".into()),
("xmlns:C".into(), "urn:ietf:params:xml:ns:caldav".into()),
];
@@ -728,91 +780,120 @@ mod tests {
tokio_buffer.flush().await.expect("tokio buffer flush");
let got = std::str::from_utf8(buffer.as_slice()).unwrap();
- return got.into()
+ return got.into();
}
#[tokio::test]
async fn basic_violation() {
- let got = serialize(
- &dav::Error::<Calendar>(vec![
- dav::Violation::Extension(Violation::ResourceMustBeNull),
- ])
- ).await;
+ let got = serialize(&dav::Error::<Calendar>(vec![dav::Violation::Extension(
+ Violation::ResourceMustBeNull,
+ )]))
+ .await;
let expected = r#"<D:error xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<C:resource-must-be-null/>
</D:error>"#;
- assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n");
+ assert_eq!(
+ &got, expected,
+ "\n---GOT---\n{got}\n---EXP---\n{expected}\n"
+ );
}
#[tokio::test]
async fn rfc_calendar_query1_req() {
- let got = serialize(
- &CalendarQuery::<Calendar> {
- selector: Some(CalendarSelector::Prop(dav::PropName(vec![
- dav::PropertyRequest::GetEtag,
- dav::PropertyRequest::Extension(PropertyRequest::CalendarData(CalendarDataRequest {
+ let got = serialize(&CalendarQuery::<Calendar> {
+ selector: Some(CalendarSelector::Prop(dav::PropName(vec![
+ dav::PropertyRequest::GetEtag,
+ dav::PropertyRequest::Extension(PropertyRequest::CalendarData(
+ CalendarDataRequest {
mime: None,
comp: Some(Comp {
name: Component::VCalendar,
- prop_kind: Some(PropKind::Prop(vec![
- CalProp {
- name: ComponentProperty("VERSION".into()),
- novalue: None,
- }
- ])),
+ prop_kind: Some(PropKind::Prop(vec![CalProp {
+ name: ComponentProperty("VERSION".into()),
+ novalue: None,
+ }])),
comp_kind: Some(CompKind::Comp(vec![
- Comp {
- name: Component::VEvent,
- prop_kind: Some(PropKind::Prop(vec![
- CalProp { name: ComponentProperty("SUMMARY".into()), novalue: None },
- CalProp { name: ComponentProperty("UID".into()), novalue: None },
- CalProp { name: ComponentProperty("DTSTART".into()), novalue: None },
- CalProp { name: ComponentProperty("DTEND".into()), novalue: None },
- CalProp { name: ComponentProperty("DURATION".into()), novalue: None },
- CalProp { name: ComponentProperty("RRULE".into()), novalue: None },
- CalProp { name: ComponentProperty("RDATE".into()), novalue: None },
- CalProp { name: ComponentProperty("EXRULE".into()), novalue: None },
- CalProp { name: ComponentProperty("EXDATE".into()), novalue: None },
- CalProp { name: ComponentProperty("RECURRENCE-ID".into()), novalue: None },
- ])),
- comp_kind: None,
- },
- Comp {
- name: Component::VTimeZone,
- prop_kind: None,
- comp_kind: None,
- }
- ])),
- }),
+ Comp {
+ name: Component::VEvent,
+ prop_kind: Some(PropKind::Prop(vec![
+ CalProp {
+ name: ComponentProperty("SUMMARY".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("UID".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("DTSTART".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("DTEND".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("DURATION".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("RRULE".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("RDATE".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("EXRULE".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("EXDATE".into()),
+ novalue: None,
+ },
+ CalProp {
+ name: ComponentProperty("RECURRENCE-ID".into()),
+ novalue: None,
+ },
+ ])),
+ comp_kind: None,
+ },
+ Comp {
+ name: Component::VTimeZone,
+ prop_kind: None,
+ comp_kind: None,
+ },
+ ])),
+ }),
recurrence: None,
limit_freebusy_set: None,
- })),
- ]))),
- filter: Filter(CompFilter {
- name: Component::VCalendar,
- additional_rules: Some(CompFilterRules::Matches(CompFilterMatch {
- time_range: None,
- prop_filter: vec![],
- comp_filter: vec![
- CompFilter {
- name: Component::VEvent,
- additional_rules: Some(CompFilterRules::Matches(CompFilterMatch {
- time_range: Some(TimeRange::FullRange(
- Utc.with_ymd_and_hms(2006,1,4,0,0,0).unwrap(),
- Utc.with_ymd_and_hms(2006,1,5,0,0,0).unwrap(),
- )),
- prop_filter: vec![],
- comp_filter: vec![],
- })),
- },
- ],
- })),
- }),
- timezone: None,
- }
- ).await;
+ },
+ )),
+ ]))),
+ filter: Filter(CompFilter {
+ name: Component::VCalendar,
+ additional_rules: Some(CompFilterRules::Matches(CompFilterMatch {
+ time_range: None,
+ prop_filter: vec![],
+ comp_filter: vec![CompFilter {
+ name: Component::VEvent,
+ additional_rules: Some(CompFilterRules::Matches(CompFilterMatch {
+ time_range: Some(TimeRange::FullRange(
+ Utc.with_ymd_and_hms(2006, 1, 4, 0, 0, 0).unwrap(),
+ Utc.with_ymd_and_hms(2006, 1, 5, 0, 0, 0).unwrap(),
+ )),
+ prop_filter: vec![],
+ comp_filter: vec![],
+ })),
+ }],
+ })),
+ }),
+ timezone: None,
+ })
+ .await;
let expected = r#"<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop>
@@ -844,59 +925,69 @@ mod tests {
</C:comp-filter>
</C:filter>
</C:calendar-query>"#;
-
- assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n");
+
+ assert_eq!(
+ &got, expected,
+ "\n---GOT---\n{got}\n---EXP---\n{expected}\n"
+ );
}
#[tokio::test]
async fn rfc_calendar_query1_res() {
- let got = serialize(
- &dav::Multistatus::<Calendar> {
- responses: vec![
- dav::Response {
- status_or_propstat: dav::StatusOrPropstat::PropStat(
- dav::Href("http://cal.example.com/bernard/work/abcd2.ics".into()),
- vec![dav::PropStat {
+ let got = serialize(&dav::Multistatus::<Calendar> {
+ responses: vec![
+ dav::Response {
+ status_or_propstat: dav::StatusOrPropstat::PropStat(
+ dav::Href("http://cal.example.com/bernard/work/abcd2.ics".into()),
+ vec![dav::PropStat {
prop: dav::AnyProp(vec![
- dav::AnyProperty::Value(dav::Property::GetEtag("\"fffff-abcd2\"".into())),
- dav::AnyProperty::Value(dav::Property::Extension(Property::CalendarData(CalendarDataPayload {
- mime: None,
- payload: "PLACEHOLDER".into()
- }))),
+ dav::AnyProperty::Value(dav::Property::GetEtag(
+ "\"fffff-abcd2\"".into(),
+ )),
+ dav::AnyProperty::Value(dav::Property::Extension(
+ Property::CalendarData(CalendarDataPayload {
+ mime: None,
+ payload: "PLACEHOLDER".into(),
+ }),
+ )),
]),
status: dav::Status(http::status::StatusCode::OK),
error: None,
responsedescription: None,
- }]
- ),
- location: None,
- error: None,
- responsedescription: None,
- },
- dav::Response {
- status_or_propstat: dav::StatusOrPropstat::PropStat(
- dav::Href("http://cal.example.com/bernard/work/abcd3.ics".into()),
- vec![dav::PropStat {
+ }],
+ ),
+ location: None,
+ error: None,
+ responsedescription: None,
+ },
+ dav::Response {
+ status_or_propstat: dav::StatusOrPropstat::PropStat(
+ dav::Href("http://cal.example.com/bernard/work/abcd3.ics".into()),
+ vec![dav::PropStat {
prop: dav::AnyProp(vec![
- dav::AnyProperty::Value(dav::Property::GetEtag("\"fffff-abcd3\"".into())),
- dav::AnyProperty::Value(dav::Property::Extension(Property::CalendarData(CalendarDataPayload{
- mime: None,
- payload: "PLACEHOLDER".into(),
- }))),
+ dav::AnyProperty::Value(dav::Property::GetEtag(
+ "\"fffff-abcd3\"".into(),
+ )),
+ dav::AnyProperty::Value(dav::Property::Extension(
+ Property::CalendarData(CalendarDataPayload {
+ mime: None,
+ payload: "PLACEHOLDER".into(),
+ }),
+ )),
]),
status: dav::Status(http::status::StatusCode::OK),
error: None,
responsedescription: None,
- }]
- ),
- location: None,
- error: None,
- responsedescription: None,
- },
- ],
- responsedescription: None,
- },
- ).await;
+ }],
+ ),
+ location: None,
+ error: None,
+ responsedescription: None,
+ },
+ ],
+ responsedescription: None,
+ })
+ .await;
let expected = r#"<D:multistatus xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:response>
@@ -921,7 +1012,9 @@ mod tests {
</D:response>
</D:multistatus>"#;
-
- assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n");
+ assert_eq!(
+ &got, expected,
+ "\n---GOT---\n{got}\n---EXP---\n{expected}\n"
+ );
}
}
diff --git a/aero-dav/src/caltypes.rs b/aero-dav/src/caltypes.rs
index 08991a0..7c85642 100644
--- a/aero-dav/src/caltypes.rs
+++ b/aero-dav/src/caltypes.rs
@@ -1,7 +1,7 @@
#![allow(dead_code)]
-use chrono::{DateTime,Utc};
use super::types as dav;
+use chrono::{DateTime, Utc};
pub const ICAL_DATETIME_FMT: &str = "%Y%m%dT%H%M%SZ";
@@ -13,7 +13,6 @@ pub const ICAL_DATETIME_FMT: &str = "%Y%m%dT%H%M%SZ";
// For reference, non-official extensions documented by SabreDAV:
// https://github.com/apple/ccs-calendarserver/tree/master/doc/Extensions
-
// ----- Root elements -----
// --- (MKCALENDAR PART) ---
@@ -33,17 +32,16 @@ pub const ICAL_DATETIME_FMT: &str = "%Y%m%dT%H%M%SZ";
#[derive(Debug, PartialEq, Clone)]
pub struct MkCalendar<E: dav::Extension>(pub dav::Set<E>);
-
/// If a response body for a successful request is included, it MUST
/// be a CALDAV:mkcalendar-response XML element.
///
/// <!ELEMENT mkcalendar-response ANY>
///
/// ----
-///
+///
/// ANY is not satisfying, so looking at RFC5689
/// https://www.rfc-editor.org/rfc/rfc5689.html#section-5.2
-///
+///
/// Definition:
///
/// <!ELEMENT mkcol-response (propstat+)>
@@ -63,9 +61,9 @@ pub enum Report<E: dav::Extension> {
/// Namespace: urn:ietf:params:xml:ns:caldav
///
/// Purpose: Defines a report for querying calendar object resources.
-///
+///
/// Description: See Section 7.8.
-///
+///
/// Definition:
///
/// <!ELEMENT calendar-query ((DAV:allprop |
@@ -131,7 +129,7 @@ pub enum PropertyRequest {
MaxDateTime,
MaxInstances,
MaxAttendeesPerInstance,
- SupportedCollationSet,
+ SupportedCollationSet,
CalendarData(CalendarDataRequest),
}
@@ -163,7 +161,7 @@ pub enum Property {
CalendarHomeSet(dav::Href),
/// Name: calendar-description
- ///
+ ///
/// Namespace: urn:ietf:params:xml:ns:caldav
///
/// Purpose: Provides a human-readable description of the calendar
@@ -192,10 +190,7 @@ pub enum Property {
/// <C:calendar-description xml:lang="fr-CA"
/// xmlns:C="urn:ietf:params:xml:ns:caldav"
/// >Calendrier de Mathilde Desruisseaux</C:calendar-description>
- CalendarDescription {
- lang: Option<String>,
- text: String,
- },
+ CalendarDescription { lang: Option<String>, text: String },
/// 5.2.2. CALDAV:calendar-timezone Property
///
@@ -232,7 +227,7 @@ pub enum Property {
/// sequence "]]>", which is the end delimiter for the CDATA section.
///
/// Definition:
- ///
+ ///
/// ```xmlschema
/// <!ELEMENT calendar-timezone (#PCDATA)>
/// PCDATA value: an iCalendar object with exactly one VTIMEZONE component.
@@ -630,7 +625,7 @@ pub enum Property {
/// WebDAV property. However, the CALDAV:calendar-data XML element is
/// not a WebDAV property and, as such, is not returned in PROPFIND
/// responses, nor used in PROPPATCH requests.
- ///
+ ///
/// Note: The iCalendar data embedded within the CALDAV:calendar-data
/// XML element MUST follow the standard XML character data encoding
/// rules, including use of &lt;, &gt;, &amp; etc. entity encoding or
@@ -649,7 +644,7 @@ pub enum Violation {
/// (CALDAV:calendar-collection-location-ok): The Request-URI MUST
/// identify a location where a calendar collection can be created;
CalendarCollectionLocationOk,
-
+
/// (CALDAV:valid-calendar-data): The time zone specified in CALDAV:
/// calendar-timezone property MUST be a valid iCalendar object
/// containing a single valid VTIMEZONE component.
@@ -712,7 +707,7 @@ pub enum Violation {
/// date-time property value (Section 5.2.6) on the calendar
/// collection where the resource will be stored;
MinDateTime,
-
+
/// (CALDAV:max-date-time): The resource submitted in the PUT request,
/// or targeted by a COPY or MOVE request, MUST have all of its
/// iCalendar DATE or DATE-TIME property values (for each recurring
@@ -784,15 +779,15 @@ pub enum Violation {
/// To deal with this, this specification makes use of the IANA Collation
/// Registry defined in [RFC4790] to specify collations that may be used
/// to carry out the text comparison operations with a well-defined rule.
-///
+///
/// The comparisons used in CalDAV are all "substring" matches, as per
/// [RFC4790], Section 4.2. Collations supported by the server MUST
/// support "substring" match operations.
-///
+///
/// CalDAV servers are REQUIRED to support the "i;ascii-casemap" and
/// "i;octet" collations, as described in [RFC4790], and MAY support
/// other collations.
-///
+///
/// Servers MUST advertise the set of collations that they support via
/// the CALDAV:supported-collation-set property defined on any resource
/// that supports reports that use collations.
@@ -807,7 +802,7 @@ pub enum Violation {
///
/// Wildcards (as defined in [RFC4790], Section 3.2) MUST NOT be used in
/// the collation identifier.
-///
+///
/// If the client chooses a collation not supported by the server, the
/// server MUST respond with a CALDAV:supported-collation precondition
/// error response.
@@ -915,7 +910,7 @@ pub struct CompSupport(pub Component);
/// Description: The CALDAV:allcomp XML element can be used when the
/// client wants all types of components returned by a calendaring
/// REPORT request.
-///
+///
/// Definition:
///
/// <!ELEMENT allcomp EMPTY>
@@ -997,7 +992,7 @@ pub enum RecurrenceModifier {
/// recurrence set into calendar components that define exactly one
/// recurrence instance, and MUST return only those whose scheduled
/// time intersect a specified time range.
-///
+///
/// The "start" attribute specifies the inclusive start of the time
/// range, and the "end" attribute specifies the non-inclusive end of
/// the time range. Both attributes are specified as date with UTC
@@ -1189,7 +1184,7 @@ pub struct CompFilterMatch {
/// Name: prop-filter
///
/// Namespace: urn:ietf:params:xml:ns:caldav
-///
+///
/// Purpose: Specifies search criteria on calendar properties.
///
/// Description: The CALDAV:prop-filter XML element specifies a query
@@ -1352,8 +1347,6 @@ pub enum ParamFilterMatch {
/// <!ELEMENT is-not-defined EMPTY>
/* CURRENTLY INLINED */
-
-
/// Name: timezone
///
/// Namespace: urn:ietf:params:xml:ns:caldav
@@ -1475,7 +1468,7 @@ impl PropertyParameter {
}
}
-#[derive(Default,Debug,PartialEq,Clone)]
+#[derive(Default, Debug, PartialEq, Clone)]
pub enum Collation {
#[default]
AsciiCaseMap,
@@ -1492,9 +1485,9 @@ impl Collation {
}
pub fn new(v: String) -> Self {
match v.as_str() {
- "i;ascii-casemap" => Self::AsciiCaseMap,
- "i;octet" => Self::Octet,
- _ => Self::Unknown(v),
+ "i;ascii-casemap" => Self::AsciiCaseMap,
+ "i;octet" => Self::Octet,
+ _ => Self::Unknown(v),
}
}
}
diff --git a/aero-dav/src/decoder.rs b/aero-dav/src/decoder.rs
index bb8d9de..bd724e8 100644
--- a/aero-dav/src/decoder.rs
+++ b/aero-dav/src/decoder.rs
@@ -1,9 +1,9 @@
-use quick_xml::events::Event;
use chrono::DateTime;
+use quick_xml::events::Event;
-use super::types::*;
use super::error::ParsingError;
-use super::xml::{Node, QRead, Reader, IRead, DAV_URN};
+use super::types::*;
+use super::xml::{IRead, Node, QRead, Reader, DAV_URN};
//@TODO (1) Rewrite all objects as Href,
// where we return Ok(None) instead of trying to find the object at any cost.
@@ -25,20 +25,21 @@ impl<E: Extension> QRead<PropFind<E>> for PropFind<E> {
if let Some(_) = xml.maybe_open(DAV_URN, "allprop").await? {
xml.close().await?;
let includ = xml.maybe_find::<Include<E>>().await?;
- break PropFind::AllProp(includ)
+ break PropFind::AllProp(includ);
}
// propname
if let Some(_) = xml.maybe_open(DAV_URN, "propname").await? {
xml.close().await?;
- break PropFind::PropName
+ break PropFind::PropName;
}
// prop
let (mut maybe_prop, mut dirty) = (None, false);
- xml.maybe_read::<PropName<E>>(&mut maybe_prop, &mut dirty).await?;
+ xml.maybe_read::<PropName<E>>(&mut maybe_prop, &mut dirty)
+ .await?;
if let Some(prop) = maybe_prop {
- break PropFind::Prop(prop)
+ break PropFind::Prop(prop);
}
// not found, skipping
@@ -80,7 +81,10 @@ impl<E: Extension> QRead<Multistatus<E>> for Multistatus<E> {
}
xml.close().await?;
- Ok(Multistatus { responses, responsedescription })
+ Ok(Multistatus {
+ responses,
+ responsedescription,
+ })
}
}
@@ -91,7 +95,8 @@ impl QRead<LockInfo> for LockInfo {
let (mut m_scope, mut m_type, mut owner) = (None, None, None);
loop {
let mut dirty = false;
- xml.maybe_read::<LockScope>(&mut m_scope, &mut dirty).await?;
+ xml.maybe_read::<LockScope>(&mut m_scope, &mut dirty)
+ .await?;
xml.maybe_read::<LockType>(&mut m_type, &mut dirty).await?;
xml.maybe_read::<Owner>(&mut owner, &mut dirty).await?;
@@ -104,7 +109,11 @@ impl QRead<LockInfo> for LockInfo {
}
xml.close().await?;
match (m_scope, m_type) {
- (Some(lockscope), Some(locktype)) => Ok(LockInfo { lockscope, locktype, owner }),
+ (Some(lockscope), Some(locktype)) => Ok(LockInfo {
+ lockscope,
+ locktype,
+ owner,
+ }),
_ => Err(ParsingError::MissingChild),
}
}
@@ -121,7 +130,6 @@ impl<E: Extension> QRead<PropValue<E>> for PropValue<E> {
}
}
-
/// Error response
impl<E: Extension> QRead<Error<E>> for Error<E> {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
@@ -132,13 +140,12 @@ impl<E: Extension> QRead<Error<E>> for Error<E> {
}
}
-
-
// ---- INNER XML
impl<E: Extension> QRead<Response<E>> for Response<E> {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
xml.open(DAV_URN, "response").await?;
- let (mut status, mut error, mut responsedescription, mut location) = (None, None, None, None);
+ let (mut status, mut error, mut responsedescription, mut location) =
+ (None, None, None, None);
let mut href = Vec::new();
let mut propstat = Vec::new();
@@ -146,28 +153,38 @@ impl<E: Extension> QRead<Response<E>> for Response<E> {
let mut dirty = false;
xml.maybe_read::<Status>(&mut status, &mut dirty).await?;
xml.maybe_push::<Href>(&mut href, &mut dirty).await?;
- xml.maybe_push::<PropStat<E>>(&mut propstat, &mut dirty).await?;
+ xml.maybe_push::<PropStat<E>>(&mut propstat, &mut dirty)
+ .await?;
xml.maybe_read::<Error<E>>(&mut error, &mut dirty).await?;
- xml.maybe_read::<ResponseDescription>(&mut responsedescription, &mut dirty).await?;
- xml.maybe_read::<Location>(&mut location, &mut dirty).await?;
+ xml.maybe_read::<ResponseDescription>(&mut responsedescription, &mut dirty)
+ .await?;
+ xml.maybe_read::<Location>(&mut location, &mut dirty)
+ .await?;
if !dirty {
match xml.peek() {
Event::End(_) => break,
- _ => { xml.skip().await? },
+ _ => xml.skip().await?,
};
}
}
xml.close().await?;
match (status, &propstat[..], &href[..]) {
- (Some(status), &[], &[_, ..]) => Ok(Response {
- status_or_propstat: StatusOrPropstat::Status(href, status),
- error, responsedescription, location,
+ (Some(status), &[], &[_, ..]) => Ok(Response {
+ status_or_propstat: StatusOrPropstat::Status(href, status),
+ error,
+ responsedescription,
+ location,
}),
(None, &[_, ..], &[_, ..]) => Ok(Response {
- status_or_propstat: StatusOrPropstat::PropStat(href.into_iter().next().unwrap(), propstat),
- error, responsedescription, location,
+ status_or_propstat: StatusOrPropstat::PropStat(
+ href.into_iter().next().unwrap(),
+ propstat,
+ ),
+ error,
+ responsedescription,
+ location,
}),
(Some(_), &[_, ..], _) => Err(ParsingError::InvalidValue),
_ => Err(ParsingError::MissingChild),
@@ -179,14 +196,17 @@ impl<E: Extension> QRead<PropStat<E>> for PropStat<E> {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
xml.open(DAV_URN, "propstat").await?;
- let (mut m_any_prop, mut m_status, mut error, mut responsedescription) = (None, None, None, None);
+ let (mut m_any_prop, mut m_status, mut error, mut responsedescription) =
+ (None, None, None, None);
loop {
let mut dirty = false;
- xml.maybe_read::<AnyProp<E>>(&mut m_any_prop, &mut dirty).await?;
+ xml.maybe_read::<AnyProp<E>>(&mut m_any_prop, &mut dirty)
+ .await?;
xml.maybe_read::<Status>(&mut m_status, &mut dirty).await?;
xml.maybe_read::<Error<E>>(&mut error, &mut dirty).await?;
- xml.maybe_read::<ResponseDescription>(&mut responsedescription, &mut dirty).await?;
+ xml.maybe_read::<ResponseDescription>(&mut responsedescription, &mut dirty)
+ .await?;
if !dirty {
match xml.peek() {
@@ -198,7 +218,12 @@ impl<E: Extension> QRead<PropStat<E>> for PropStat<E> {
xml.close().await?;
match (m_any_prop, m_status) {
- (Some(prop), Some(status)) => Ok(PropStat { prop, status, error, responsedescription }),
+ (Some(prop), Some(status)) => Ok(PropStat {
+ prop,
+ status,
+ error,
+ responsedescription,
+ }),
_ => Err(ParsingError::MissingChild),
}
}
@@ -208,8 +233,12 @@ impl QRead<Status> for Status {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
xml.open(DAV_URN, "status").await?;
let fullcode = xml.tag_string().await?;
- let txtcode = fullcode.splitn(3, ' ').nth(1).ok_or(ParsingError::InvalidValue)?;
- let code = http::status::StatusCode::from_bytes(txtcode.as_bytes()).or(Err(ParsingError::InvalidValue))?;
+ let txtcode = fullcode
+ .splitn(3, ' ')
+ .nth(1)
+ .ok_or(ParsingError::InvalidValue)?;
+ let code = http::status::StatusCode::from_bytes(txtcode.as_bytes())
+ .or(Err(ParsingError::InvalidValue))?;
xml.close().await?;
Ok(Status(code))
}
@@ -263,27 +292,55 @@ impl<E: Extension> QRead<Set<E>> for Set<E> {
impl<E: Extension> QRead<Violation<E>> for Violation<E> {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
- if xml.maybe_open(DAV_URN, "lock-token-matches-request-uri").await?.is_some() {
+ if xml
+ .maybe_open(DAV_URN, "lock-token-matches-request-uri")
+ .await?
+ .is_some()
+ {
xml.close().await?;
Ok(Violation::LockTokenMatchesRequestUri)
- } else if xml.maybe_open(DAV_URN, "lock-token-submitted").await?.is_some() {
+ } else if xml
+ .maybe_open(DAV_URN, "lock-token-submitted")
+ .await?
+ .is_some()
+ {
let links = xml.collect::<Href>().await?;
xml.close().await?;
Ok(Violation::LockTokenSubmitted(links))
- } else if xml.maybe_open(DAV_URN, "no-conflicting-lock").await?.is_some() {
+ } else if xml
+ .maybe_open(DAV_URN, "no-conflicting-lock")
+ .await?
+ .is_some()
+ {
let links = xml.collect::<Href>().await?;
xml.close().await?;
Ok(Violation::NoConflictingLock(links))
- } else if xml.maybe_open(DAV_URN, "no-external-entities").await?.is_some() {
+ } else if xml
+ .maybe_open(DAV_URN, "no-external-entities")
+ .await?
+ .is_some()
+ {
xml.close().await?;
Ok(Violation::NoExternalEntities)
- } else if xml.maybe_open(DAV_URN, "preserved-live-properties").await?.is_some() {
+ } else if xml
+ .maybe_open(DAV_URN, "preserved-live-properties")
+ .await?
+ .is_some()
+ {
xml.close().await?;
Ok(Violation::PreservedLiveProperties)
- } else if xml.maybe_open(DAV_URN, "propfind-finite-depth").await?.is_some() {
+ } else if xml
+ .maybe_open(DAV_URN, "propfind-finite-depth")
+ .await?
+ .is_some()
+ {
xml.close().await?;
Ok(Violation::PropfindFiniteDepth)
- } else if xml.maybe_open(DAV_URN, "cannot-modify-protected-property").await?.is_some() {
+ } else if xml
+ .maybe_open(DAV_URN, "cannot-modify-protected-property")
+ .await?
+ .is_some()
+ {
xml.close().await?;
Ok(Violation::CannotModifyProtectedProperty)
} else {
@@ -323,7 +380,7 @@ impl<E: Extension> QRead<AnyProperty<E>> for AnyProperty<E> {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
match Property::qread(xml).await {
Err(ParsingError::Recoverable) => (),
- otherwise => return otherwise.map(Self::Value)
+ otherwise => return otherwise.map(Self::Value),
}
PropertyRequest::qread(xml).await.map(Self::Request)
}
@@ -335,7 +392,11 @@ impl<E: Extension> QRead<PropertyRequest<E>> for PropertyRequest<E> {
Some(PropertyRequest::CreationDate)
} else if xml.maybe_open(DAV_URN, "displayname").await?.is_some() {
Some(PropertyRequest::DisplayName)
- } else if xml.maybe_open(DAV_URN, "getcontentlanguage").await?.is_some() {
+ } else if xml
+ .maybe_open(DAV_URN, "getcontentlanguage")
+ .await?
+ .is_some()
+ {
Some(PropertyRequest::GetContentLanguage)
} else if xml.maybe_open(DAV_URN, "getcontentlength").await?.is_some() {
Some(PropertyRequest::GetContentLength)
@@ -359,8 +420,10 @@ impl<E: Extension> QRead<PropertyRequest<E>> for PropertyRequest<E> {
Some(pr) => {
xml.close().await?;
Ok(pr)
- },
- None => E::PropertyRequest::qread(xml).await.map(PropertyRequest::Extension),
+ }
+ None => E::PropertyRequest::qread(xml)
+ .await
+ .map(PropertyRequest::Extension),
}
}
}
@@ -368,46 +431,86 @@ impl<E: Extension> QRead<PropertyRequest<E>> for PropertyRequest<E> {
impl<E: Extension> QRead<Property<E>> for Property<E> {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
// Core WebDAV properties
- if xml.maybe_open_start(DAV_URN, "creationdate").await?.is_some() {
+ if xml
+ .maybe_open_start(DAV_URN, "creationdate")
+ .await?
+ .is_some()
+ {
let datestr = xml.tag_string().await?;
xml.close().await?;
- return Ok(Property::CreationDate(DateTime::parse_from_rfc3339(datestr.as_str())?))
- } else if xml.maybe_open_start(DAV_URN, "displayname").await?.is_some() {
+ return Ok(Property::CreationDate(DateTime::parse_from_rfc3339(
+ datestr.as_str(),
+ )?));
+ } else if xml
+ .maybe_open_start(DAV_URN, "displayname")
+ .await?
+ .is_some()
+ {
let name = xml.tag_string().await?;
xml.close().await?;
- return Ok(Property::DisplayName(name))
- } else if xml.maybe_open_start(DAV_URN, "getcontentlanguage").await?.is_some() {
+ return Ok(Property::DisplayName(name));
+ } else if xml
+ .maybe_open_start(DAV_URN, "getcontentlanguage")
+ .await?
+ .is_some()
+ {
let lang = xml.tag_string().await?;
xml.close().await?;
- return Ok(Property::GetContentLanguage(lang))
- } else if xml.maybe_open_start(DAV_URN, "getcontentlength").await?.is_some() {
+ return Ok(Property::GetContentLanguage(lang));
+ } else if xml
+ .maybe_open_start(DAV_URN, "getcontentlength")
+ .await?
+ .is_some()
+ {
let cl = xml.tag_string().await?.parse::<u64>()?;
xml.close().await?;
- return Ok(Property::GetContentLength(cl))
- } else if xml.maybe_open_start(DAV_URN, "getcontenttype").await?.is_some() {
+ return Ok(Property::GetContentLength(cl));
+ } else if xml
+ .maybe_open_start(DAV_URN, "getcontenttype")
+ .await?
+ .is_some()
+ {
let ct = xml.tag_string().await?;
xml.close().await?;
- return Ok(Property::GetContentType(ct))
+ return Ok(Property::GetContentType(ct));
} else if xml.maybe_open_start(DAV_URN, "getetag").await?.is_some() {
let etag = xml.tag_string().await?;
xml.close().await?;
- return Ok(Property::GetEtag(etag))
- } else if xml.maybe_open_start(DAV_URN, "getlastmodified").await?.is_some() {
+ return Ok(Property::GetEtag(etag));
+ } else if xml
+ .maybe_open_start(DAV_URN, "getlastmodified")
+ .await?
+ .is_some()
+ {
let datestr = xml.tag_string().await?;
xml.close().await?;
- return Ok(Property::GetLastModified(DateTime::parse_from_rfc2822(datestr.as_str())?))
- } else if xml.maybe_open_start(DAV_URN, "lockdiscovery").await?.is_some() {
+ return Ok(Property::GetLastModified(DateTime::parse_from_rfc2822(
+ datestr.as_str(),
+ )?));
+ } else if xml
+ .maybe_open_start(DAV_URN, "lockdiscovery")
+ .await?
+ .is_some()
+ {
let acc = xml.collect::<ActiveLock>().await?;
xml.close().await?;
- return Ok(Property::LockDiscovery(acc))
- } else if xml.maybe_open_start(DAV_URN, "resourcetype").await?.is_some() {
+ return Ok(Property::LockDiscovery(acc));
+ } else if xml
+ .maybe_open_start(DAV_URN, "resourcetype")
+ .await?
+ .is_some()
+ {
let acc = xml.collect::<ResourceType<E>>().await?;
xml.close().await?;
- return Ok(Property::ResourceType(acc))
- } else if xml.maybe_open_start(DAV_URN, "supportedlock").await?.is_some() {
+ return Ok(Property::ResourceType(acc));
+ } else if xml
+ .maybe_open_start(DAV_URN, "supportedlock")
+ .await?
+ .is_some()
+ {
let acc = xml.collect::<LockEntry>().await?;
xml.close().await?;
- return Ok(Property::SupportedLock(acc))
+ return Ok(Property::SupportedLock(acc));
}
// Option 2: an extension property, delegating
@@ -418,31 +521,49 @@ impl<E: Extension> QRead<Property<E>> for Property<E> {
impl QRead<ActiveLock> for ActiveLock {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
xml.open(DAV_URN, "activelock").await?;
- let (mut m_scope, mut m_type, mut m_depth, mut owner, mut timeout, mut locktoken, mut m_root) =
- (None, None, None, None, None, None, None);
+ let (
+ mut m_scope,
+ mut m_type,
+ mut m_depth,
+ mut owner,
+ mut timeout,
+ mut locktoken,
+ mut m_root,
+ ) = (None, None, None, None, None, None, None);
loop {
let mut dirty = false;
- xml.maybe_read::<LockScope>(&mut m_scope, &mut dirty).await?;
+ xml.maybe_read::<LockScope>(&mut m_scope, &mut dirty)
+ .await?;
xml.maybe_read::<LockType>(&mut m_type, &mut dirty).await?;
xml.maybe_read::<Depth>(&mut m_depth, &mut dirty).await?;
xml.maybe_read::<Owner>(&mut owner, &mut dirty).await?;
xml.maybe_read::<Timeout>(&mut timeout, &mut dirty).await?;
- xml.maybe_read::<LockToken>(&mut locktoken, &mut dirty).await?;
+ xml.maybe_read::<LockToken>(&mut locktoken, &mut dirty)
+ .await?;
xml.maybe_read::<LockRoot>(&mut m_root, &mut dirty).await?;
if !dirty {
match xml.peek() {
Event::End(_) => break,
- _ => { xml.skip().await?; },
+ _ => {
+ xml.skip().await?;
+ }
}
}
}
xml.close().await?;
match (m_scope, m_type, m_depth, m_root) {
- (Some(lockscope), Some(locktype), Some(depth), Some(lockroot)) =>
- Ok(ActiveLock { lockscope, locktype, depth, owner, timeout, locktoken, lockroot }),
+ (Some(lockscope), Some(locktype), Some(depth), Some(lockroot)) => Ok(ActiveLock {
+ lockscope,
+ locktype,
+ depth,
+ owner,
+ timeout,
+ locktoken,
+ lockroot,
+ }),
_ => Err(ParsingError::MissingChild),
}
}
@@ -465,7 +586,7 @@ impl QRead<Depth> for Depth {
impl QRead<Owner> for Owner {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
xml.open(DAV_URN, "owner").await?;
-
+
let mut owner = Owner::Unknown;
loop {
match xml.peek() {
@@ -475,17 +596,21 @@ impl QRead<Owner> for Owner {
owner = Owner::Txt(txt);
}
}
- Event::Start(_) | Event::Empty(_) => {
- match Href::qread(xml).await {
- Ok(href) => { owner = Owner::Href(href); },
- Err(ParsingError::Recoverable) => { xml.skip().await?; },
- Err(e) => return Err(e),
+ Event::Start(_) | Event::Empty(_) => match Href::qread(xml).await {
+ Ok(href) => {
+ owner = Owner::Href(href);
}
- }
+ Err(ParsingError::Recoverable) => {
+ xml.skip().await?;
+ }
+ Err(e) => return Err(e),
+ },
Event::End(_) => break,
- _ => { xml.skip().await?; },
+ _ => {
+ xml.skip().await?;
+ }
}
- };
+ }
xml.close().await?;
Ok(owner)
}
@@ -495,7 +620,7 @@ impl QRead<Timeout> for Timeout {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
const SEC_PFX: &str = "Second-";
xml.open(DAV_URN, "timeout").await?;
-
+
let timeout = match xml.tag_string().await?.as_str() {
"Infinite" => Timeout::Infinite,
seconds => match seconds.strip_prefix(SEC_PFX) {
@@ -531,10 +656,12 @@ impl<E: Extension> QRead<ResourceType<E>> for ResourceType<E> {
async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
if xml.maybe_open(DAV_URN, "collection").await?.is_some() {
xml.close().await?;
- return Ok(ResourceType::Collection)
+ return Ok(ResourceType::Collection);
}
-
- E::ResourceType::qread(xml).await.map(ResourceType::Extension)
+
+ E::ResourceType::qread(xml)
+ .await
+ .map(ResourceType::Extension)
}
}
@@ -545,8 +672,10 @@ impl QRead<LockEntry> for LockEntry {
loop {
let mut dirty = false;
- xml.maybe_read::<LockScope>(&mut maybe_scope, &mut dirty).await?;
- xml.maybe_read::<LockType>(&mut maybe_type, &mut dirty).await?;
+ xml.maybe_read::<LockScope>(&mut maybe_scope, &mut dirty)
+ .await?;
+ xml.maybe_read::<LockType>(&mut maybe_type, &mut dirty)
+ .await?;
if !dirty {
match xml.peek() {
Event::End(_) => break,
@@ -557,7 +686,10 @@ impl QRead<LockEntry> for LockEntry {
xml.close().await?;
match (maybe_scope, maybe_type) {
- (Some(lockscope), Some(locktype)) => Ok(LockEntry { lockscope, locktype }),
+ (Some(lockscope), Some(locktype)) => Ok(LockEntry {
+ lockscope,
+ locktype,
+ }),
_ => Err(ParsingError::MissingChild),
}
}
@@ -570,12 +702,12 @@ impl QRead<LockScope> for LockScope {
let lockscope = loop {
if xml.maybe_open(DAV_URN, "exclusive").await?.is_some() {
xml.close().await?;
- break LockScope::Exclusive
- }
+ break LockScope::Exclusive;
+ }
if xml.maybe_open(DAV_URN, "shared").await?.is_some() {
xml.close().await?;
- break LockScope::Shared
+ break LockScope::Shared;
}
xml.skip().await?;
@@ -593,7 +725,7 @@ impl QRead<LockType> for LockType {
let locktype = loop {
if xml.maybe_open(DAV_URN, "write").await?.is_some() {
xml.close().await?;
- break LockType::Write
+ break LockType::Write;
}
xml.skip().await?;
@@ -616,8 +748,8 @@ impl QRead<Href> for Href {
#[cfg(test)]
mod tests {
use super::*;
- use chrono::{FixedOffset, TimeZone};
use crate::realization::Core;
+ use chrono::{FixedOffset, TimeZone};
use quick_xml::reader::NsReader;
#[tokio::test]
@@ -630,8 +762,10 @@ mod tests {
</D:propfind>
"#;
- let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap();
- let got = rdr.find::<PropFind::<Core>>().await.unwrap();
+ let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes()))
+ .await
+ .unwrap();
+ let got = rdr.find::<PropFind<Core>>().await.unwrap();
assert_eq!(got, PropFind::<Core>::PropName);
}
@@ -654,18 +788,23 @@ mod tests {
</D:propfind>
"#;
- let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap();
- let got = rdr.find::<PropFind::<Core>>().await.unwrap();
-
- assert_eq!(got, PropFind::Prop(PropName(vec![
- PropertyRequest::DisplayName,
- PropertyRequest::GetContentLength,
- PropertyRequest::GetContentType,
- PropertyRequest::GetEtag,
- PropertyRequest::GetLastModified,
- PropertyRequest::ResourceType,
- PropertyRequest::SupportedLock,
- ])));
+ let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes()))
+ .await
+ .unwrap();
+ let got = rdr.find::<PropFind<Core>>().await.unwrap();
+
+ assert_eq!(
+ got,
+ PropFind::Prop(PropName(vec![
+ PropertyRequest::DisplayName,
+ PropertyRequest::GetContentLength,
+ PropertyRequest::GetContentType,
+ PropertyRequest::GetEtag,
+ PropertyRequest::GetLastModified,
+ PropertyRequest::ResourceType,
+ PropertyRequest::SupportedLock,
+ ]))
+ );
}
#[tokio::test]
@@ -677,17 +816,19 @@ mod tests {
</D:lock-token-submitted>
</D:error>"#;
- let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap();
- let got = rdr.find::<Error::<Core>>().await.unwrap();
-
- assert_eq!(got, Error(vec![
- Violation::LockTokenSubmitted(vec![
- Href("/locked/".into())
- ])
- ]));
+ let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes()))
+ .await
+ .unwrap();
+ let got = rdr.find::<Error<Core>>().await.unwrap();
+
+ assert_eq!(
+ got,
+ Error(vec![Violation::LockTokenSubmitted(vec![Href(
+ "/locked/".into()
+ )])])
+ );
}
-
#[tokio::test]
async fn rfc_propertyupdate() {
let src = r#"<?xml version="1.0" encoding="utf-8" ?>
@@ -706,13 +847,18 @@ mod tests {
</D:remove>
</D:propertyupdate>"#;
- let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap();
- let got = rdr.find::<PropertyUpdate::<Core>>().await.unwrap();
+ let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes()))
+ .await
+ .unwrap();
+ let got = rdr.find::<PropertyUpdate<Core>>().await.unwrap();
- assert_eq!(got, PropertyUpdate(vec![
- PropertyUpdateItem::Set(Set(PropValue(vec![]))),
- PropertyUpdateItem::Remove(Remove(PropName(vec![]))),
- ]));
+ assert_eq!(
+ got,
+ PropertyUpdate(vec![
+ PropertyUpdateItem::Set(Set(PropValue(vec![]))),
+ PropertyUpdateItem::Remove(Remove(PropName(vec![]))),
+ ])
+ );
}
#[tokio::test]
@@ -728,14 +874,21 @@ mod tests {
</D:lockinfo>
"#;
- let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap();
+ let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes()))
+ .await
+ .unwrap();
let got = rdr.find::<LockInfo>().await.unwrap();
- assert_eq!(got, LockInfo {
- lockscope: LockScope::Exclusive,
- locktype: LockType::Write,
- owner: Some(Owner::Href(Href("http://example.org/~ejw/contact.html".into()))),
- });
+ assert_eq!(
+ got,
+ LockInfo {
+ lockscope: LockScope::Exclusive,
+ locktype: LockType::Write,
+ owner: Some(Owner::Href(Href(
+ "http://example.org/~ejw/contact.html".into()
+ ))),
+ }
+ );
}
#[tokio::test]
@@ -777,59 +930,63 @@ mod tests {
</multistatus>
"#;
- let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap();
- let got = rdr.find::<Multistatus::<Core>>().await.unwrap();
-
- assert_eq!(got, Multistatus {
- responses: vec![
- Response {
- status_or_propstat: StatusOrPropstat::PropStat(
- Href("http://www.example.com/container/".into()),
- vec![PropStat {
- prop: AnyProp(vec![
- AnyProperty::Request(PropertyRequest::CreationDate),
- AnyProperty::Request(PropertyRequest::DisplayName),
- AnyProperty::Request(PropertyRequest::ResourceType),
- AnyProperty::Request(PropertyRequest::SupportedLock),
- ]),
- status: Status(http::status::StatusCode::OK),
- error: None,
- responsedescription: None,
- }],
- ),
- error: None,
- responsedescription: None,
- location: None,
- },
- Response {
- status_or_propstat: StatusOrPropstat::PropStat(
- Href("http://www.example.com/container/front.html".into()),
- vec![PropStat {
- prop: AnyProp(vec![
- AnyProperty::Request(PropertyRequest::CreationDate),
- AnyProperty::Request(PropertyRequest::DisplayName),
- AnyProperty::Request(PropertyRequest::GetContentLength),
- AnyProperty::Request(PropertyRequest::GetContentType),
- AnyProperty::Request(PropertyRequest::GetEtag),
- AnyProperty::Request(PropertyRequest::GetLastModified),
- AnyProperty::Request(PropertyRequest::ResourceType),
- AnyProperty::Request(PropertyRequest::SupportedLock),
- ]),
- status: Status(http::status::StatusCode::OK),
- error: None,
- responsedescription: None,
- }],
- ),
- error: None,
- responsedescription: None,
- location: None,
- },
- ],
- responsedescription: None,
- });
+ let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes()))
+ .await
+ .unwrap();
+ let got = rdr.find::<Multistatus<Core>>().await.unwrap();
+
+ assert_eq!(
+ got,
+ Multistatus {
+ responses: vec![
+ Response {
+ status_or_propstat: StatusOrPropstat::PropStat(
+ Href("http://www.example.com/container/".into()),
+ vec![PropStat {
+ prop: AnyProp(vec![
+ AnyProperty::Request(PropertyRequest::CreationDate),
+ AnyProperty::Request(PropertyRequest::DisplayName),
+ AnyProperty::Request(PropertyRequest::ResourceType),
+ AnyProperty::Request(PropertyRequest::SupportedLock),
+ ]),
+ status: Status(http::status::StatusCode::OK),
+ error: None,
+ responsedescription: None,
+ }],
+ ),
+ error: None,
+ responsedescription: None,
+ location: None,
+ },
+ Response {
+ status_or_propstat: StatusOrPropstat::PropStat(
+ Href("http://www.example.com/container/front.html".into()),
+ vec![PropStat {
+ prop: AnyProp(vec![
+ AnyProperty::Request(PropertyRequest::CreationDate),
+ AnyProperty::Request(PropertyRequest::DisplayName),
+ AnyProperty::Request(PropertyRequest::GetContentLength),
+ AnyProperty::Request(PropertyRequest::GetContentType),
+ AnyProperty::Request(PropertyRequest::GetEtag),
+ AnyProperty::Request(PropertyRequest::GetLastModified),
+ AnyProperty::Request(PropertyRequest::ResourceType),
+ AnyProperty::Request(PropertyRequest::SupportedLock),
+ ]),
+ status: Status(http::status::StatusCode::OK),
+ error: None,
+ responsedescription: None,
+ }],
+ ),
+ error: None,
+ responsedescription: None,
+ location: None,
+ },
+ ],
+ responsedescription: None,
+ }
+ );
}
-
#[tokio::test]
async fn rfc_multistatus_value() {
let src = r#"
@@ -888,78 +1045,103 @@ mod tests {
</D:response>
</D:multistatus>"#;
- let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap();
- let got = rdr.find::<Multistatus::<Core>>().await.unwrap();
-
- assert_eq!(got, Multistatus {
- responses: vec![
- Response {
- status_or_propstat: StatusOrPropstat::PropStat(
- Href("/container/".into()),
- vec![PropStat {
- prop: AnyProp(vec![
- AnyProperty::Value(Property::CreationDate(FixedOffset::west_opt(8 * 3600).unwrap().with_ymd_and_hms(1997, 12, 01, 17, 42, 21).unwrap())),
- AnyProperty::Value(Property::DisplayName("Example collection".into())),
- AnyProperty::Value(Property::ResourceType(vec![ResourceType::Collection])),
- AnyProperty::Value(Property::SupportedLock(vec![
- LockEntry {
- lockscope: LockScope::Exclusive,
- locktype: LockType::Write,
- },
- LockEntry {
- lockscope: LockScope::Shared,
- locktype: LockType::Write,
- },
- ])),
- ]),
- status: Status(http::status::StatusCode::OK),
- error: None,
- responsedescription: None,
- }],
- ),
- error: None,
- responsedescription: None,
- location: None,
-
- },
- Response {
- status_or_propstat: StatusOrPropstat::PropStat(
- Href("/container/front.html".into()),
- vec![PropStat {
- prop: AnyProp(vec![
- AnyProperty::Value(Property::CreationDate(FixedOffset::west_opt(8 * 3600).unwrap().with_ymd_and_hms(1997, 12, 01, 18, 27, 21).unwrap())),
- AnyProperty::Value(Property::DisplayName("Example HTML resource".into())),
- AnyProperty::Value(Property::GetContentLength(4525)),
- AnyProperty::Value(Property::GetContentType("text/html".into())),
- AnyProperty::Value(Property::GetEtag(r#""zzyzx""#.into())),
- AnyProperty::Value(Property::GetLastModified(FixedOffset::west_opt(0).unwrap().with_ymd_and_hms(1998, 01, 12, 09, 25, 56).unwrap())),
- //@FIXME know bug, can't disambiguate between an empty resource
- //type value and a request resource type
- AnyProperty::Request(PropertyRequest::ResourceType),
- AnyProperty::Value(Property::SupportedLock(vec![
- LockEntry {
- lockscope: LockScope::Exclusive,
- locktype: LockType::Write,
- },
- LockEntry {
- lockscope: LockScope::Shared,
- locktype: LockType::Write,
- },
- ])),
- ]),
- status: Status(http::status::StatusCode::OK),
- error: None,
- responsedescription: None,
- }],
- ),
- error: None,
- responsedescription: None,
- location: None,
-
- },
- ],
- responsedescription: None,
- });
+ let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes()))
+ .await
+ .unwrap();
+ let got = rdr.find::<Multistatus<Core>>().await.unwrap();
+
+ assert_eq!(
+ got,
+ Multistatus {
+ responses: vec![
+ Response {
+ status_or_propstat: StatusOrPropstat::PropStat(
+ Href("/container/".into()),
+ vec![PropStat {
+ prop: AnyProp(vec![
+ AnyProperty::Value(Property::CreationDate(
+ FixedOffset::west_opt(8 * 3600)
+ .unwrap()
+ .with_ymd_and_hms(1997, 12, 01, 17, 42, 21)
+ .unwrap()
+ )),
+ AnyProperty::Value(Property::DisplayName(
+ "Example collection".into()
+ )),
+ AnyProperty::Value(Property::ResourceType(vec![
+ ResourceType::Collection
+ ])),
+ AnyProperty::Value(Property::SupportedLock(vec![
+ LockEntry {
+ lockscope: LockScope::Exclusive,
+ locktype: LockType::Write,
+ },
+ LockEntry {
+ lockscope: LockScope::Shared,
+ locktype: LockType::Write,
+ },
+ ])),
+ ]),
+ status: Status(http::status::StatusCode::OK),
+ error: None,
+ responsedescription: None,
+ }],
+ ),
+ error: None,
+ responsedescription: None,
+ location: None,
+ },
+ Response {
+ status_or_propstat: StatusOrPropstat::PropStat(
+ Href("/container/front.html".into()),
+ vec![PropStat {
+ prop: AnyProp(vec![
+ AnyProperty::Value(Property::CreationDate(
+ FixedOffset::west_opt(8 * 3600)
+ .unwrap()
+ .with_ymd_and_hms(1997, 12, 01, 18, 27, 21)
+ .unwrap()
+ )),
+ AnyProperty::Value(Property::DisplayName(
+ "Example HTML resource".into()
+ )),
+ AnyProperty::Value(Property::GetContentLength(4525)),
+ AnyProperty::Value(Property::GetContentType(
+ "text/html".into()
+ )),
+ AnyProperty::Value(Property::GetEtag(r#""zzyzx""#.into())),
+ AnyProperty::Value(Property::GetLastModified(
+ FixedOffset::west_opt(0)
+ .unwrap()
+ .with_ymd_and_hms(1998, 01, 12, 09, 25, 56)
+ .unwrap()
+ )),
+ //@FIXME know bug, can't disambiguate between an empty resource
+ //type value and a request resource type
+ AnyProperty::Request(PropertyRequest::ResourceType),
+ AnyProperty::Value(Property::SupportedLock(vec![
+ LockEntry {
+ lockscope: LockScope::Exclusive,
+ locktype: LockType::Write,
+ },
+ LockEntry {
+ lockscope: LockScope::Shared,
+ locktype: LockType::Write,
+ },
+ ])),
+ ]),
+ status: Status(http::status::StatusCode::OK),
+ error: None,
+ responsedescription: None,
+ }],
+ ),
+ error: None,
+ responsedescription: None,
+ location: None,
+ },
+ ],
+ responsedescription: None,
+ }
+ );
}
-
}
diff --git a/aero-dav/src/encoder.rs b/aero-dav/src/encoder.rs
index 1320c8a..fd87e95 100644
--- a/aero-dav/src/encoder.rs
+++ b/aero-dav/src/encoder.rs
@@ -1,8 +1,7 @@
-use quick_xml::Error as QError;
-use quick_xml::events::{Event, BytesText};
use super::types::*;
-use super::xml::{Node, Writer,QWrite,IWrite};
-
+use super::xml::{IWrite, Node, QWrite, Writer};
+use quick_xml::events::{BytesText, Event};
+use quick_xml::Error as QError;
// --- XML ROOTS
@@ -16,15 +15,17 @@ impl<E: Extension> QWrite for PropFind<E> {
match self {
Self::PropName => {
let empty_propname = xml.create_dav_element("propname");
- xml.q.write_event_async(Event::Empty(empty_propname)).await?
- },
+ xml.q
+ .write_event_async(Event::Empty(empty_propname))
+ .await?
+ }
Self::AllProp(maybe_include) => {
let empty_allprop = xml.create_dav_element("allprop");
xml.q.write_event_async(Event::Empty(empty_allprop)).await?;
if let Some(include) = maybe_include {
include.qwrite(xml).await?;
}
- },
+ }
Self::Prop(propname) => propname.qwrite(xml).await?,
}
xml.q.write_event_async(Event::End(end)).await
@@ -45,9 +46,8 @@ impl<E: Extension> QWrite for PropertyUpdate<E> {
}
}
-
/// PROPFIND RESPONSE, PROPPATCH RESPONSE, COPY RESPONSE, MOVE RESPONSE
-/// DELETE RESPONSE,
+/// DELETE RESPONSE,
impl<E: Extension> QWrite for Multistatus<E> {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
let start = xml.create_dav_element("multistatus");
@@ -140,7 +140,6 @@ impl<E: Extension> QWrite for Remove<E> {
}
}
-
impl<E: Extension> QWrite for PropName<E> {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
let start = xml.create_dav_element("prop");
@@ -176,14 +175,15 @@ impl<E: Extension> QWrite for AnyProperty<E> {
}
}
-
impl QWrite for Href {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
let start = xml.create_dav_element("href");
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(&self.0))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(&self.0)))
+ .await?;
xml.q.write_event_async(Event::End(end)).await
}
}
@@ -216,9 +216,9 @@ impl<E: Extension> QWrite for StatusOrPropstat<E> {
href.qwrite(xml).await?;
}
status.qwrite(xml).await
- },
+ }
Self::PropStat(href, propstat_list) => {
- href.qwrite(xml).await?;
+ href.qwrite(xml).await?;
for propstat in propstat_list.iter() {
propstat.qwrite(xml).await?;
}
@@ -235,8 +235,14 @@ impl QWrite for Status {
xml.q.write_event_async(Event::Start(start.clone())).await?;
- let txt = format!("HTTP/1.1 {} {}", self.0.as_str(), self.0.canonical_reason().unwrap_or("No reason"));
- xml.q.write_event_async(Event::Text(BytesText::new(&txt))).await?;
+ let txt = format!(
+ "HTTP/1.1 {} {}",
+ self.0.as_str(),
+ self.0.canonical_reason().unwrap_or("No reason")
+ );
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(&txt)))
+ .await?;
xml.q.write_event_async(Event::End(end)).await?;
@@ -250,7 +256,9 @@ impl QWrite for ResponseDescription {
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(&self.0))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(&self.0)))
+ .await?;
xml.q.write_event_async(Event::End(end)).await
}
}
@@ -296,62 +304,76 @@ impl<E: Extension> QWrite for Property<E> {
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(&date.to_rfc3339()))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(&date.to_rfc3339())))
+ .await?;
xml.q.write_event_async(Event::End(end)).await?;
- },
+ }
DisplayName(name) => {
// <D:displayname>Example collection</D:displayname>
let start = xml.create_dav_element("displayname");
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(name))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(name)))
+ .await?;
xml.q.write_event_async(Event::End(end)).await?;
- },
+ }
GetContentLanguage(lang) => {
let start = xml.create_dav_element("getcontentlanguage");
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(lang))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(lang)))
+ .await?;
xml.q.write_event_async(Event::End(end)).await?;
- },
+ }
GetContentLength(len) => {
// <D:getcontentlength>4525</D:getcontentlength>
let start = xml.create_dav_element("getcontentlength");
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(&len.to_string()))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(&len.to_string())))
+ .await?;
xml.q.write_event_async(Event::End(end)).await?;
- },
+ }
GetContentType(ct) => {
// <D:getcontenttype>text/html</D:getcontenttype>
let start = xml.create_dav_element("getcontenttype");
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(&ct))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(&ct)))
+ .await?;
xml.q.write_event_async(Event::End(end)).await?;
- },
+ }
GetEtag(et) => {
// <D:getetag>"zzyzx"</D:getetag>
let start = xml.create_dav_element("getetag");
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(et))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(et)))
+ .await?;
xml.q.write_event_async(Event::End(end)).await?;
- },
+ }
GetLastModified(date) => {
// <D:getlastmodified>Mon, 12 Jan 1998 09:25:56 GMT</D:getlastmodified>
let start = xml.create_dav_element("getlastmodified");
let end = start.to_end();
xml.q.write_event_async(Event::Start(start.clone())).await?;
- xml.q.write_event_async(Event::Text(BytesText::new(&date.to_rfc2822()))).await?;
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(&date.to_rfc2822())))
+ .await?;
xml.q.write_event_async(Event::End(end)).await?;
- },
+ }
LockDiscovery(many_locks) => {
// <D:lockdiscovery><D:activelock> ... </D:activelock></D:lockdiscovery>
let start = xml.create_dav_element("lockdiscovery");
@@ -362,17 +384,17 @@ impl<E: Extension> QWrite for Property<E> {
lock.qwrite(xml).await?;
}
xml.q.write_event_async(Event::End(end)).await?;
- },
+ }
ResourceType(many_types) => {
// <D:resourcetype><D:collection/></D:resourcetype>
-
+
// <D:resourcetype/>
-
+
// <x:resourcetype xmlns:x="DAV:">
// <x:collection/>
// <f:search-results xmlns:f="http://www.example.com/ns"/>
// </x:resourcetype>
-
+
let start = xml.create_dav_element("resourcetype");
if many_types.is_empty() {
xml.q.write_event_async(Event::Empty(start)).await?;
@@ -384,7 +406,7 @@ impl<E: Extension> QWrite for Property<E> {
}
xml.q.write_event_async(Event::End(end)).await?;
}
- },
+ }
SupportedLock(many_entries) => {
// <D:supportedlock/>
@@ -401,7 +423,7 @@ impl<E: Extension> QWrite for Property<E> {
}
xml.q.write_event_async(Event::End(end)).await?;
}
- },
+ }
Extension(inner) => inner.qwrite(xml).await?,
};
Ok(())
@@ -413,8 +435,10 @@ impl<E: Extension> QWrite for ResourceType<E> {
match self {
Self::Collection => {
let empty_collection = xml.create_dav_element("collection");
- xml.q.write_event_async(Event::Empty(empty_collection)).await
- },
+ xml.q
+ .write_event_async(Event::Empty(empty_collection))
+ .await
+ }
Self::Extension(inner) => inner.qwrite(xml).await,
}
}
@@ -425,7 +449,7 @@ impl<E: Extension> QWrite for Include<E> {
let start = xml.create_dav_element("include");
let end = start.to_end();
- xml.q.write_event_async(Event::Start(start.clone())).await?;
+ xml.q.write_event_async(Event::Start(start.clone())).await?;
for prop in self.0.iter() {
prop.qwrite(xml).await?;
}
@@ -505,8 +529,8 @@ impl QWrite for LockType {
Self::Write => {
let empty_write = xml.create_dav_element("write");
xml.q.write_event_async(Event::Empty(empty_write)).await?
- },
- };
+ }
+ };
xml.q.write_event_async(Event::End(end)).await
}
}
@@ -521,12 +545,12 @@ impl QWrite for LockScope {
Self::Exclusive => {
let empty_tag = xml.create_dav_element("exclusive");
xml.q.write_event_async(Event::Empty(empty_tag)).await?
- },
+ }
Self::Shared => {
let empty_tag = xml.create_dav_element("shared");
xml.q.write_event_async(Event::Empty(empty_tag)).await?
- },
- };
+ }
+ };
xml.q.write_event_async(Event::End(end)).await
}
}
@@ -538,7 +562,11 @@ impl QWrite for Owner {
xml.q.write_event_async(Event::Start(start.clone())).await?;
match self {
- Self::Txt(txt) => xml.q.write_event_async(Event::Text(BytesText::new(&txt))).await?,
+ Self::Txt(txt) => {
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(&txt)))
+ .await?
+ }
Self::Href(href) => href.qwrite(xml).await?,
Self::Unknown => (),
}
@@ -553,9 +581,21 @@ impl QWrite for Depth {
xml.q.write_event_async(Event::Start(start.clone())).await?;
match self {
- Self::Zero => xml.q.write_event_async(Event::Text(BytesText::new("0"))).await?,
- Self::One => xml.q.write_event_async(Event::Text(BytesText::new("1"))).await?,
- Self::Infinity => xml.q.write_event_async(Event::Text(BytesText::new("infinity"))).await?,
+ Self::Zero => {
+ xml.q
+ .write_event_async(Event::Text(BytesText::new("0")))
+ .await?
+ }
+ Self::One => {
+ xml.q
+ .write_event_async(Event::Text(BytesText::new("1")))
+ .await?
+ }
+ Self::Infinity => {
+ xml.q
+ .write_event_async(Event::Text(BytesText::new("infinity")))
+ .await?
+ }
};
xml.q.write_event_async(Event::End(end)).await
}
@@ -570,9 +610,15 @@ impl QWrite for Timeout {
match self {
Self::Seconds(count) => {
let txt = format!("Second-{}", count);
- xml.q.write_event_async(Event::Text(BytesText::new(&txt))).await?
- },
- Self::Infinite => xml.q.write_event_async(Event::Text(BytesText::new("Infinite"))).await?
+ xml.q
+ .write_event_async(Event::Text(BytesText::new(&txt)))
+ .await?
+ }
+ Self::Infinite => {
+ xml.q
+ .write_event_async(Event::Text(BytesText::new("Infinite")))
+ .await?
+ }
};
xml.q.write_event_async(Event::End(end)).await
}
@@ -620,8 +666,10 @@ impl<E: Extension> QWrite for Violation<E> {
};
match self {
- Violation::LockTokenMatchesRequestUri => atom("lock-token-matches-request-uri").await,
- Violation::LockTokenSubmitted(hrefs) if hrefs.is_empty() => atom("lock-token-submitted").await,
+ Violation::LockTokenMatchesRequestUri => atom("lock-token-matches-request-uri").await,
+ Violation::LockTokenSubmitted(hrefs) if hrefs.is_empty() => {
+ atom("lock-token-submitted").await
+ }
Violation::LockTokenSubmitted(hrefs) => {
let start = xml.create_dav_element("lock-token-submitted");
let end = start.to_end();
@@ -631,8 +679,10 @@ impl<E: Extension> QWrite for Violation<E> {
href.qwrite(xml).await?;
}
xml.q.write_event_async(Event::End(end)).await
- },
- Violation::NoConflictingLock(hrefs) if hrefs.is_empty() => atom("no-conflicting-lock").await,
+ }
+ Violation::NoConflictingLock(hrefs) if hrefs.is_empty() => {
+ atom("no-conflicting-lock").await
+ }
Violation::NoConflictingLock(hrefs) => {
let start = xml.create_dav_element("no-conflicting-lock");
let end = start.to_end();
@@ -642,11 +692,13 @@ impl<E: Extension> QWrite for Violation<E> {
href.qwrite(xml).await?;
}
xml.q.write_event_async(Event::End(end)).await
- },
+ }
Violation::NoExternalEntities => atom("no-external-entities").await,
Violation::PreservedLiveProperties => atom("preserved-live-properties").await,
Violation::PropfindFiniteDepth => atom("propfind-finite-depth").await,
- Violation::CannotModifyProtectedProperty => atom("cannot-modify-protected-property").await,
+ Violation::CannotModifyProtectedProperty => {
+ atom("cannot-modify-protected-property").await
+ }
Violation::Extension(inner) => inner.qwrite(xml).await,
}
}
@@ -654,30 +706,32 @@ impl<E: Extension> QWrite for Violation<E> {
#[cfg(test)]
mod tests {
- use super::*;
use super::super::xml;
+ use super::*;
use crate::realization::Core;
use tokio::io::AsyncWriteExt;
/// To run only the unit tests and avoid the behavior ones:
/// cargo test --bin aerogramme
-
+
async fn serialize(elem: &impl QWrite) -> String {
let mut buffer = Vec::new();
let mut tokio_buffer = tokio::io::BufWriter::new(&mut buffer);
let q = quick_xml::writer::Writer::new_with_indent(&mut tokio_buffer, b' ', 4);
- let ns_to_apply = vec![ ("xmlns:D".into(), "DAV:".into()) ];
+ let ns_to_apply = vec![("xmlns:D".into(), "DAV:".into())];
let mut writer = Writer { q, ns_to_apply };
elem.qwrite(&mut writer).await.expect("xml serialization");
tokio_buffer.flush().await.expect("tokio buffer flush");
let got = std::str::from_utf8(buffer.as_slice()).unwrap();
- return got.into()
+ return got.into();
}
async fn deserialize<T: xml::Node<T>>(src: &str) -> T {
- let mut rdr = xml::Reader::new(quick_xml::reader::NsReader::from_reader(src.as_bytes())).await.unwrap();
+ let mut rdr = xml::Reader::new(quick_xml::reader::NsReader::from_reader(src.as_bytes()))
+ .await
+ .unwrap();
rdr.find().await.unwrap()
}
@@ -688,15 +742,18 @@ mod tests {
let got = serialize(&orig).await;
let expected = r#"<D:href xmlns:D="DAV:">/SOGo/dav/so/</D:href>"#;
- assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n");
+ assert_eq!(
+ &got, expected,
+ "\n---GOT---\n{got}\n---EXP---\n{expected}\n"
+ );
assert_eq!(deserialize::<Href>(got.as_str()).await, orig)
}
#[tokio::test]
async fn basic_multistatus() {
- let orig = Multistatus::<Core> {
- responses: vec![],
- responsedescription: Some(ResponseDescription("Hello world".into()))
+ let orig = Multistatus::<Core> {
+ responses: vec![],
+ responsedescription: Some(ResponseDescription("Hello world".into())),
};
let got = serialize(&orig).await;
@@ -704,18 +761,18 @@ mod tests {
<D:responsedescription>Hello world</D:responsedescription>
</D:multistatus>"#;
- assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n");
+ assert_eq!(
+ &got, expected,
+ "\n---GOT---\n{got}\n---EXP---\n{expected}\n"
+ );
assert_eq!(deserialize::<Multistatus::<Core>>(got.as_str()).await, orig)
}
-
#[tokio::test]
async fn rfc_error_delete_locked() {
- let orig = Error::<Core>(vec![
- Violation::LockTokenSubmitted(vec![
- Href("/locked/".into())
- ])
- ]);
+ let orig = Error::<Core>(vec![Violation::LockTokenSubmitted(vec![Href(
+ "/locked/".into(),
+ )])]);
let got = serialize(&orig).await;
let expected = r#"<D:error xmlns:D="DAV:">
@@ -724,7 +781,10 @@ mod tests {
</D:lock-token-submitted>
</D:error>"#;
- assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n");
+ assert_eq!(
+ &got, expected,
+ "\n---GOT---\n{got}\n---EXP---\n{expected}\n"
+ );
assert_eq!(deserialize::<Error<Core>>(got.as_str()).await, orig)
}
@@ -738,7 +798,10 @@ mod tests {
<D:propname/>
</D:propfind>"#;
- assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n");
+ assert_eq!(
+ &got, expected,
+ "\n---GOT---\n{got}\n---EXP---\n{expected}\n"
+ );
assert_eq!(deserialize::<PropFind::<Core>>(got.as_str()).await, orig)
}
@@ -759,7 +822,7 @@ mod tests {
status: Status(http::status::StatusCode::OK),
error: None,
responsedescription: None,
- }]
+ }],
),
error: None,
responsedescription: None,
@@ -782,8 +845,8 @@ mod tests {
status: Status(http::status::StatusCode::OK),
error: None,
responsedescription: None,
- }
- ]),
+ }],
+ ),
error: None,
responsedescription: None,
location: None,
@@ -825,8 +888,10 @@ mod tests {
</D:response>
</D:multistatus>"#;
-
- assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n");
+ assert_eq!(
+ &got, expected,
+ "\n---GOT---\n{got}\n---EXP---\n{expected}\n"
+ );
assert_eq!(deserialize::<Multistatus::<Core>>(got.as_str()).await, orig)
}
@@ -835,17 +900,20 @@ mod tests {
let orig = PropFind::<Core>::AllProp(None);
let got = serialize(&orig).await;
- let expected = r#"<D:propfind xmlns:D="DAV:">
+ let expected = r#"<D:propfind xmlns:D="DAV:">
<D:allprop/>
</D:propfind>"#;
- assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n");
+ assert_eq!(
+ &got, expected,
+ "\n---GOT---\n{got}\n---EXP---\n{expected}\n"
+ );
assert_eq!(deserialize::<PropFind::<Core>>(got.as_str()).await, orig)
}
#[tokio::test]
async fn rfc_allprop_res() {
- use chrono::{FixedOffset,TimeZone};
+ use chrono::{FixedOffset, TimeZone};
let orig = Multistatus::<Core> {
responses: vec![
@@ -853,28 +921,34 @@ mod tests {
status_or_propstat: StatusOrPropstat::PropStat(
Href("/container/".into()),
vec![PropStat {
- prop: AnyProp(vec![
- AnyProperty::Value(Property::CreationDate(FixedOffset::west_opt(8 * 3600)
- .unwrap()
- .with_ymd_and_hms(1997, 12, 1, 17, 42, 21)
- .unwrap())),
- AnyProperty::Value(Property::DisplayName("Example collection".into())),
- AnyProperty::Value(Property::ResourceType(vec![ResourceType::Collection])),
- AnyProperty::Value(Property::SupportedLock(vec![
- LockEntry {
- lockscope: LockScope::Exclusive,
- locktype: LockType::Write,
- },
- LockEntry {
- lockscope: LockScope::Shared,
- locktype: LockType::Write,
- },
- ])),
- ]),
- status: Status(http::status::StatusCode::OK),
- error: None,
- responsedescription: None,
- }]
+ prop: AnyProp(vec![
+ AnyProperty::Value(Property::CreationDate(
+ FixedOffset::west_opt(8 * 3600)
+ .unwrap()
+ .with_ymd_and_hms(1997, 12, 1, 17, 42, 21)
+ .unwrap(),
+ )),
+ AnyProperty::Value(Property::DisplayName(
+ "Example collection".into(),
+ )),
+ AnyProperty::Value(Property::ResourceType(vec![
+ ResourceType::Collection,
+ ])),
+ AnyProperty::Value(Property::SupportedLock(vec![
+ LockEntry {
+ lockscope: LockScope::Exclusive,
+ locktype: LockType::Write,
+ },
+ LockEntry {
+ lockscope: LockScope::Shared,
+ locktype: LockType::Write,
+ },
+ ])),
+ ]),
+ status: Status(http::status::StatusCode::OK),
+ error: None,
+ responsedescription: None,
+ }],
),
error: None,
responsedescription: None,
@@ -884,37 +958,43 @@ mod tests {
status_or_propstat: StatusOrPropstat::PropStat(
Href("/container/front.html".into()),
vec![PropStat {
- prop: AnyProp(vec![
- AnyProperty::Value(Property::CreationDate(FixedOffset::west_opt(8 * 3600)
- .unwrap()
- .with_ymd_and_hms(1997, 12, 1, 18, 27, 21)
- .unwrap())),
- AnyProperty::Value(Property::DisplayName("Example HTML resource".into())),
- AnyProperty::Value(Property::GetContentLength(4525)),
- AnyProperty::Value(Property::GetContentType("text/html".into())),
- AnyProperty::Value(Property::GetEtag(r#""zzyzx""#.into())),
- AnyProperty::Value(Property::GetLastModified(FixedOffset::east_opt(0)
- .unwrap()
- .with_ymd_and_hms(1998, 1, 12, 9, 25, 56)
- .unwrap())),
- //@FIXME know bug, can't disambiguate between an empty resource
- //type value and a request resource type
- AnyProperty::Request(PropertyRequest::ResourceType),
- AnyProperty::Value(Property::SupportedLock(vec![
- LockEntry {
- lockscope: LockScope::Exclusive,
- locktype: LockType::Write,
- },
- LockEntry {
- lockscope: LockScope::Shared,
- locktype: LockType::Write,
- },
- ])),
- ]),
- status: Status(http::status::StatusCode::OK),
- error: None,
- responsedescription: None,
- }]
+ prop: AnyProp(vec![
+ AnyProperty::Value(Property::CreationDate(
+ FixedOffset::west_opt(8 * 3600)
+ .unwrap()
+ .with_ymd_and_hms(1997, 12, 1, 18, 27, 21)
+ .unwrap(),
+ )),
+ AnyProperty::Value(Property::DisplayName(
+ "Example HTML resource".into(),
+ )),
+ AnyProperty::Value(Property::GetContentLength(4525)),
+ AnyProperty::Value(Property::GetContentType("text/html".into())),
+ AnyProperty::Value(Property::GetEtag(r#""zzyzx""#.into())),
+ AnyProperty::Value(Property::GetLastModified(
+ FixedOffset::east_opt(0)
+ .unwrap()
+ .with_ymd_and_hms(1998, 1, 12, 9, 25, 56)
+ .unwrap(),
+ )),
+ //@FIXME know bug, can't disambiguate between an empty resource
+ //type value and a request resource type
+ AnyProperty::Request(PropertyRequest::ResourceType),
+ AnyProperty::Value(Property::SupportedLock(vec![
+ LockEntry {
+ lockscope: LockScope::Exclusive,
+ locktype: LockType::Write,
+ },
+ LockEntry {
+ lockscope: LockScope::Shared,
+ locktype: LockType::Write,
+ },
+ ])),
+ ]),
+ status: Status(http::status::StatusCode::OK),
+ error: None,
+ responsedescription: None,
+ }],
),
error: None,
responsedescription: None,
@@ -993,15 +1073,18 @@ mod tests {
</D:response>
</D:multistatus>"#;
- assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n");
+ assert_eq!(
+ &got, expected,
+ "\n---GOT---\n{got}\n---EXP---\n{expected}\n"
+ );
assert_eq!(deserialize::<Multistatus::<Core>>(got.as_str()).await, orig)
}
#[tokio::test]
async fn rfc_allprop_include() {
let orig = PropFind::<Core>::AllProp(Some(Include(vec![
- PropertyRequest::DisplayName,
- PropertyRequest::ResourceType,
+ PropertyRequest::DisplayName,
+ PropertyRequest::ResourceType,
])));
let got = serialize(&orig).await;
@@ -1014,19 +1097,20 @@ mod tests {
</D:include>
</D:propfind>"#;
- assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n");
+ assert_eq!(
+ &got, expected,
+ "\n---GOT---\n{got}\n---EXP---\n{expected}\n"
+ );
assert_eq!(deserialize::<PropFind::<Core>>(got.as_str()).await, orig)
}
#[tokio::test]
async fn rfc_propertyupdate() {
let orig = PropertyUpdate::<Core>(vec![
- PropertyUpdateItem::Set(Set(PropValue(vec![
- Property::GetContentLanguage("fr-FR".into()),
- ]))),
- PropertyUpdateItem::Remove(Remove(PropName(vec![
- PropertyRequest::DisplayName,
- ]))),
+ PropertyUpdateItem::Set(Set(PropValue(vec![Property::GetContentLanguage(
+ "fr-FR".into(),
+ )]))),
+ PropertyUpdateItem::Remove(Remove(PropName(vec![PropertyRequest::DisplayName]))),
]);
let got = serialize(&orig).await;
@@ -1043,8 +1127,14 @@ mod tests {
</D:remove>
</D:propertyupdate>"#;
- assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n");
- assert_eq!(deserialize::<PropertyUpdate::<Core>>(got.as_str()).await, orig)
+ assert_eq!(
+ &got, expected,
+ "\n---GOT---\n{got}\n---EXP---\n{expected}\n"
+ );
+ assert_eq!(
+ deserialize::<PropertyUpdate::<Core>>(got.as_str()).await,
+ orig
+ )
}
#[tokio::test]
@@ -1053,7 +1143,7 @@ mod tests {
responses: vec![Response {
status_or_propstat: StatusOrPropstat::Status(
vec![Href("http://www.example.com/container/resource3".into())],
- Status(http::status::StatusCode::from_u16(423).unwrap())
+ Status(http::status::StatusCode::from_u16(423).unwrap()),
),
error: Some(Error(vec![Violation::LockTokenSubmitted(vec![])])),
responsedescription: None,
@@ -1074,7 +1164,10 @@ mod tests {
</D:response>
</D:multistatus>"#;
- assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n");
+ assert_eq!(
+ &got, expected,
+ "\n---GOT---\n{got}\n---EXP---\n{expected}\n"
+ );
assert_eq!(deserialize::<Multistatus::<Core>>(got.as_str()).await, orig)
}
@@ -1083,7 +1176,9 @@ mod tests {
let orig = LockInfo {
lockscope: LockScope::Exclusive,
locktype: LockType::Write,
- owner: Some(Owner::Href(Href("http://example.org/~ejw/contact.html".into()))),
+ owner: Some(Owner::Href(Href(
+ "http://example.org/~ejw/contact.html".into(),
+ ))),
};
let got = serialize(&orig).await;
@@ -1100,23 +1195,30 @@ mod tests {
</D:owner>
</D:lockinfo>"#;
- assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n");
+ assert_eq!(
+ &got, expected,
+ "\n---GOT---\n{got}\n---EXP---\n{expected}\n"
+ );
assert_eq!(deserialize::<LockInfo>(got.as_str()).await, orig)
}
#[tokio::test]
async fn rfc_simple_lock_response() {
- let orig = PropValue::<Core>(vec![
- Property::LockDiscovery(vec![ActiveLock {
- lockscope: LockScope::Exclusive,
- locktype: LockType::Write,
- depth: Depth::Infinity,
- owner: Some(Owner::Href(Href("http://example.org/~ejw/contact.html".into()))),
- timeout: Some(Timeout::Seconds(604800)),
- locktoken: Some(LockToken(Href("urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4".into()))),
- lockroot: LockRoot(Href("http://example.com/workspace/webdav/proposal.doc".into())),
- }]),
- ]);
+ let orig = PropValue::<Core>(vec![Property::LockDiscovery(vec![ActiveLock {
+ lockscope: LockScope::Exclusive,
+ locktype: LockType::Write,
+ depth: Depth::Infinity,
+ owner: Some(Owner::Href(Href(
+ "http://example.org/~ejw/contact.html".into(),
+ ))),
+ timeout: Some(Timeout::Seconds(604800)),
+ locktoken: Some(LockToken(Href(
+ "urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4".into(),
+ ))),
+ lockroot: LockRoot(Href(
+ "http://example.com/workspace/webdav/proposal.doc".into(),
+ )),
+ }])]);
let got = serialize(&orig).await;
@@ -1144,7 +1246,10 @@ mod tests {
</D:lockdiscovery>
</D:prop>"#;
- assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n");
+ assert_eq!(
+ &got, expected,
+ "\n---GOT---\n{got}\n---EXP---\n{expected}\n"
+ );
assert_eq!(deserialize::<PropValue::<Core>>(got.as_str()).await, orig)
}
}
diff --git a/aero-dav/src/error.rs b/aero-dav/src/error.rs
index 570f779..c8f1de1 100644
--- a/aero-dav/src/error.rs
+++ b/aero-dav/src/error.rs
@@ -10,10 +10,10 @@ pub enum ParsingError {
TagNotFound,
InvalidValue,
Utf8Error(std::str::Utf8Error),
- QuickXml(quick_xml::Error),
+ QuickXml(quick_xml::Error),
Chrono(chrono::format::ParseError),
Int(std::num::ParseIntError),
- Eof
+ Eof,
}
impl std::fmt::Display for ParsingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
diff --git a/aero-dav/src/lib.rs b/aero-dav/src/lib.rs
index 009951a..7507ddc 100644
--- a/aero-dav/src/lib.rs
+++ b/aero-dav/src/lib.rs
@@ -7,19 +7,19 @@ pub mod error;
pub mod xml;
// webdav
-pub mod types;
-pub mod encoder;
pub mod decoder;
+pub mod encoder;
+pub mod types;
// calendar
-pub mod caltypes;
-pub mod calencoder;
pub mod caldecoder;
+pub mod calencoder;
+pub mod caltypes;
// acl (wip)
-pub mod acltypes;
-pub mod aclencoder;
pub mod acldecoder;
+pub mod aclencoder;
+pub mod acltypes;
// versioning (wip)
mod versioningtypes;
diff --git a/aero-dav/src/realization.rs b/aero-dav/src/realization.rs
index bfed4d7..7283e68 100644
--- a/aero-dav/src/realization.rs
+++ b/aero-dav/src/realization.rs
@@ -1,8 +1,8 @@
-use super::types as dav;
-use super::caltypes as cal;
use super::acltypes as acl;
-use super::xml;
+use super::caltypes as cal;
use super::error;
+use super::types as dav;
+use super::xml;
#[derive(Debug, PartialEq, Clone)]
pub struct Disabled(());
@@ -12,12 +12,15 @@ impl xml::QRead<Disabled> for Disabled {
}
}
impl xml::QWrite for Disabled {
- async fn qwrite(&self, _xml: &mut xml::Writer<impl xml::IWrite>) -> Result<(), quick_xml::Error> {
+ async fn qwrite(
+ &self,
+ _xml: &mut xml::Writer<impl xml::IWrite>,
+ ) -> Result<(), quick_xml::Error> {
unreachable!()
}
}
-/// The base WebDAV
+/// The base WebDAV
///
/// Any extension is disabled through an object we can't build
/// due to a private inner element.
@@ -33,8 +36,7 @@ impl dav::Extension for Core {
// WebDAV with the base Calendar implementation (RFC4791)
#[derive(Debug, PartialEq, Clone)]
pub struct Calendar {}
-impl dav::Extension for Calendar
-{
+impl dav::Extension for Calendar {
type Error = cal::Violation;
type Property = cal::Property;
type PropertyRequest = cal::PropertyRequest;
@@ -44,8 +46,7 @@ impl dav::Extension for Calendar
// ACL
#[derive(Debug, PartialEq, Clone)]
pub struct Acl {}
-impl dav::Extension for Acl
-{
+impl dav::Extension for Acl {
type Error = Disabled;
type Property = acl::Property;
type PropertyRequest = acl::PropertyRequest;
@@ -77,7 +78,10 @@ impl xml::QRead<Property> for Property {
}
}
impl xml::QWrite for Property {
- async fn qwrite(&self, xml: &mut xml::Writer<impl xml::IWrite>) -> Result<(), quick_xml::Error> {
+ async fn qwrite(
+ &self,
+ xml: &mut xml::Writer<impl xml::IWrite>,
+ ) -> Result<(), quick_xml::Error> {
match self {
Self::Cal(c) => c.qwrite(xml).await,
Self::Acl(a) => a.qwrite(xml).await,
@@ -96,11 +100,16 @@ impl xml::QRead<PropertyRequest> for PropertyRequest {
Err(error::ParsingError::Recoverable) => (),
otherwise => return otherwise.map(PropertyRequest::Cal),
}
- acl::PropertyRequest::qread(xml).await.map(PropertyRequest::Acl)
+ acl::PropertyRequest::qread(xml)
+ .await
+ .map(PropertyRequest::Acl)
}
}
impl xml::QWrite for PropertyRequest {
- async fn qwrite(&self, xml: &mut xml::Writer<impl xml::IWrite>) -> Result<(), quick_xml::Error> {
+ async fn qwrite(
+ &self,
+ xml: &mut xml::Writer<impl xml::IWrite>,
+ ) -> Result<(), quick_xml::Error> {
match self {
Self::Cal(c) => c.qwrite(xml).await,
Self::Acl(a) => a.qwrite(xml).await,
@@ -123,7 +132,10 @@ impl xml::QRead<ResourceType> for ResourceType {
}
}
impl xml::QWrite for ResourceType {
- async fn qwrite(&self, xml: &mut xml::Writer<impl xml::IWrite>) -> Result<(), quick_xml::Error> {
+ async fn qwrite(
+ &self,
+ xml: &mut xml::Writer<impl xml::IWrite>,
+ ) -> Result<(), quick_xml::Error> {
match self {
Self::Cal(c) => c.qwrite(xml).await,
Self::Acl(a) => a.qwrite(xml).await,
diff --git a/aero-dav/src/types.rs b/aero-dav/src/types.rs
index 9457a8f..d5466da 100644
--- a/aero-dav/src/types.rs
+++ b/aero-dav/src/types.rs
@@ -1,8 +1,8 @@
#![allow(dead_code)]
use std::fmt::Debug;
-use chrono::{DateTime,FixedOffset};
use super::xml;
+use chrono::{DateTime, FixedOffset};
/// It's how we implement a DAV extension
/// (That's the dark magic part...)
@@ -42,7 +42,7 @@ pub struct ActiveLock {
///
/// <!ELEMENT collection EMPTY >
#[derive(Debug, PartialEq)]
-pub struct Collection{}
+pub struct Collection {}
/// 14.4 depth XML Element
///
@@ -58,7 +58,7 @@ pub struct Collection{}
pub enum Depth {
Zero,
One,
- Infinity
+ Infinity,
}
/// 14.5 error XML Element
@@ -153,7 +153,6 @@ pub enum Violation<E: Extension> {
/// PROPFIND requests on collections.
PropfindFiniteDepth,
-
/// Name: cannot-modify-protected-property
///
/// Use with: 403 Forbidden
@@ -172,7 +171,7 @@ pub enum Violation<E: Extension> {
/// Name: exclusive
///
/// Purpose: Specifies an exclusive lock.
-///
+///
/// <!ELEMENT exclusive EMPTY >
#[derive(Debug, PartialEq)]
pub struct Exclusive {}
@@ -193,7 +192,6 @@ pub struct Exclusive {}
#[derive(Debug, PartialEq, Clone)]
pub struct Href(pub String);
-
/// 14.8. include XML Element
///
/// Name: include
@@ -280,7 +278,7 @@ pub struct LockRoot(pub Href);
#[derive(Debug, PartialEq, Clone)]
pub enum LockScope {
Exclusive,
- Shared
+ Shared,
}
/// 14.14. locktoken XML Element
@@ -288,7 +286,7 @@ pub enum LockScope {
/// Name: locktoken
///
/// Purpose: The lock token associated with a lock.
-///
+///
/// Description: The href contains a single lock token URI, which
/// refers to the lock.
///
@@ -314,7 +312,7 @@ pub enum LockType {
///
///
/// <!ELEMENT write EMPTY >
- Write
+ Write,
}
/// 14.16. multistatus XML Element
@@ -477,7 +475,6 @@ pub struct PropStat<E: Extension> {
pub responsedescription: Option<ResponseDescription>,
}
-
/// 14.23. remove XML Element
///
/// Name: remove
@@ -579,15 +576,14 @@ pub struct Set<E: Extension>(pub PropValue<E>);
#[derive(Debug, PartialEq, Clone)]
pub struct Shared {}
-
/// 14.28. status XML Element
-///
+///
/// Name: status
///
/// Purpose: Holds a single HTTP status-line.
///
/// Value: status-line (defined in Section 6.1 of [RFC2616])
-///
+///
/// <!ELEMENT status (#PCDATA) >
//@FIXME: Better typing is possible with an enum for example
#[derive(Debug, PartialEq, Clone)]
@@ -624,7 +620,6 @@ pub enum Timeout {
Infinite,
}
-
/// 15. DAV Properties
///
/// For DAV properties, the name of the property is also the same as the
@@ -704,7 +699,7 @@ pub enum Property<E: Extension> {
CreationDate(DateTime<FixedOffset>),
/// 15.2. displayname Property
- ///
+ ///
/// Name: displayname
///
/// Purpose: Provides a name for the resource that is suitable for
@@ -734,7 +729,6 @@ pub enum Property<E: Extension> {
/// <!ELEMENT displayname (#PCDATA) >
DisplayName(String),
-
/// 15.3. getcontentlanguage Property
///
/// Name: getcontentlanguage
@@ -893,7 +887,6 @@ pub enum Property<E: Extension> {
/// <!ELEMENT lockdiscovery (activelock)* >
LockDiscovery(Vec<ActiveLock>),
-
/// 15.9. resourcetype Property
///
/// Name: resourcetype
@@ -920,7 +913,7 @@ pub enum Property<E: Extension> {
/// type.
///
/// Example: (fictional example to show extensibility)
- ///
+ ///
/// <x:resourcetype xmlns:x="DAV:">
/// <x:collection/>
/// <f:search-results xmlns:f="http://www.example.com/ns"/>
diff --git a/aero-dav/src/xml.rs b/aero-dav/src/xml.rs
index d57093e..c89f531 100644
--- a/aero-dav/src/xml.rs
+++ b/aero-dav/src/xml.rs
@@ -1,8 +1,8 @@
use futures::Future;
-use quick_xml::events::{Event, BytesStart};
+use quick_xml::events::{BytesStart, Event};
use quick_xml::name::ResolveResult;
use quick_xml::reader::NsReader;
-use tokio::io::{AsyncWrite, AsyncBufRead};
+use tokio::io::{AsyncBufRead, AsyncWrite};
use super::error::ParsingError;
@@ -17,7 +17,10 @@ pub trait IRead = AsyncBufRead + Unpin;
// Serialization/Deserialization traits
pub trait QWrite {
- fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> impl Future<Output = Result<(), quick_xml::Error>> + Send;
+ fn qwrite(
+ &self,
+ xml: &mut Writer<impl IWrite>,
+ ) -> impl Future<Output = Result<(), quick_xml::Error>> + Send;
}
pub trait QRead<T> {
fn qread(xml: &mut Reader<impl IRead>) -> impl Future<Output = Result<T, ParsingError>>;
@@ -44,7 +47,11 @@ impl<T: IWrite> Writer<T> {
fn create_ns_element(&mut self, ns: &str, name: &str) -> BytesStart<'static> {
let mut start = BytesStart::new(format!("{}:{}", ns, name));
if !self.ns_to_apply.is_empty() {
- start.extend_attributes(self.ns_to_apply.iter().map(|(k, n)| (k.as_str(), n.as_str())));
+ start.extend_attributes(
+ self.ns_to_apply
+ .iter()
+ .map(|(k, n)| (k.as_str(), n.as_str())),
+ );
self.ns_to_apply.clear()
}
start
@@ -66,16 +73,26 @@ impl<T: IRead> Reader<T> {
let parents = vec![];
let prev = Event::Eof;
buf.clear();
- Ok(Self { cur, prev, parents, rdr, buf })
+ Ok(Self {
+ cur,
+ prev,
+ parents,
+ rdr,
+ buf,
+ })
}
/// read one more tag
/// do not expose it publicly
async fn next(&mut self) -> Result<Event<'static>, ParsingError> {
- let evt = self.rdr.read_event_into_async(&mut self.buf).await?.into_owned();
- self.buf.clear();
- self.prev = std::mem::replace(&mut self.cur, evt);
- Ok(self.prev.clone())
+ let evt = self
+ .rdr
+ .read_event_into_async(&mut self.buf)
+ .await?
+ .into_owned();
+ self.buf.clear();
+ self.prev = std::mem::replace(&mut self.cur, evt);
+ Ok(self.prev.clone())
}
/// skip a node at current level
@@ -84,9 +101,12 @@ impl<T: IRead> Reader<T> {
//println!("skipping inside node {:?} value {:?}", self.parents.last(), self.cur);
match &self.cur {
Event::Start(b) => {
- let _span = self.rdr.read_to_end_into_async(b.to_end().name(), &mut self.buf).await?;
+ let _span = self
+ .rdr
+ .read_to_end_into_async(b.to_end().name(), &mut self.buf)
+ .await?;
self.next().await
- },
+ }
Event::End(_) => Err(ParsingError::WrongToken),
Event::Eof => Err(ParsingError::Eof),
_ => self.next().await,
@@ -100,13 +120,13 @@ impl<T: IRead> Reader<T> {
Event::End(be) => be.name(),
_ => return false,
};
-
+
let (extr_ns, local) = self.rdr.resolve_element(qname);
if local.into_inner() != key.as_bytes() {
- return false
+ return false;
}
-
+
match extr_ns {
ResolveResult::Bound(v) => v.into_inner() == ns,
_ => false,
@@ -142,7 +162,7 @@ impl<T: IRead> Reader<T> {
Event::CData(unescaped) => {
acc.push_str(std::str::from_utf8(unescaped.as_ref())?);
self.next().await?
- },
+ }
Event::Text(escaped) => {
acc.push_str(escaped.unescape()?.as_ref());
self.next().await?
@@ -153,33 +173,41 @@ impl<T: IRead> Reader<T> {
}
}
- pub async fn maybe_read<N: Node<N>>(&mut self, t: &mut Option<N>, dirty: &mut bool) -> Result<(), ParsingError> {
+ pub async fn maybe_read<N: Node<N>>(
+ &mut self,
+ t: &mut Option<N>,
+ dirty: &mut bool,
+ ) -> Result<(), ParsingError> {
if !self.parent_has_child() {
- return Ok(())
+ return Ok(());
}
match N::qread(self).await {
- Ok(v) => {
- *t = Some(v);
+ Ok(v) => {
+ *t = Some(v);
*dirty = true;
- Ok(())
- },
+ Ok(())
+ }
Err(ParsingError::Recoverable) => Ok(()),
Err(e) => Err(e),
}
}
- pub async fn maybe_push<N: Node<N>>(&mut self, t: &mut Vec<N>, dirty: &mut bool) -> Result<(), ParsingError> {
+ pub async fn maybe_push<N: Node<N>>(
+ &mut self,
+ t: &mut Vec<N>,
+ dirty: &mut bool,
+ ) -> Result<(), ParsingError> {
if !self.parent_has_child() {
- return Ok(())
+ return Ok(());
}
match N::qread(self).await {
- Ok(v) => {
- t.push(v);
+ Ok(v) => {
+ t.push(v);
*dirty = true;
- Ok(())
- },
+ Ok(())
+ }
Err(ParsingError::Recoverable) => Ok(()),
Err(e) => Err(e),
}
@@ -220,7 +248,7 @@ impl<T: IRead> Reader<T> {
pub async fn collect<N: Node<N>>(&mut self) -> Result<Vec<N>, ParsingError> {
let mut acc = Vec::new();
if !self.parent_has_child() {
- return Ok(acc)
+ return Ok(acc);
}
loop {
@@ -229,7 +257,7 @@ impl<T: IRead> Reader<T> {
Event::End(_) => return Ok(acc),
_ => {
self.skip().await?;
- },
+ }
},
Ok(v) => acc.push(v),
Err(e) => return Err(e),
@@ -242,13 +270,13 @@ impl<T: IRead> Reader<T> {
let evt = match self.peek() {
Event::Empty(_) if self.is_tag(ns, key) => {
// hack to make `prev_attr` works
- // here we duplicate the current tag
- // as in other words, we virtually moved one token
+ // here we duplicate the current tag
+ // as in other words, we virtually moved one token
// which is useful for prev_attr and any logic based on
// self.prev + self.open() on empty nodes
self.prev = self.cur.clone();
self.cur.clone()
- },
+ }
Event::Start(_) if self.is_tag(ns, key) => self.next().await?,
_ => return Err(ParsingError::Recoverable),
};
@@ -258,7 +286,11 @@ impl<T: IRead> Reader<T> {
Ok(evt)
}
- pub async fn open_start(&mut self, ns: &[u8], key: &str) -> Result<Event<'static>, ParsingError> {
+ pub async fn open_start(
+ &mut self,
+ ns: &[u8],
+ key: &str,
+ ) -> Result<Event<'static>, ParsingError> {
//println!("try open start tag {:?}, on {:?}", key, self.peek());
let evt = match self.peek() {
Event::Start(_) if self.is_tag(ns, key) => self.next().await?,
@@ -270,7 +302,11 @@ impl<T: IRead> Reader<T> {
Ok(evt)
}
- pub async fn maybe_open(&mut self, ns: &[u8], key: &str) -> Result<Option<Event<'static>>, ParsingError> {
+ pub async fn maybe_open(
+ &mut self,
+ ns: &[u8],
+ key: &str,
+ ) -> Result<Option<Event<'static>>, ParsingError> {
match self.open(ns, key).await {
Ok(v) => Ok(Some(v)),
Err(ParsingError::Recoverable) => Ok(None),
@@ -278,7 +314,11 @@ impl<T: IRead> Reader<T> {
}
}
- pub async fn maybe_open_start(&mut self, ns: &[u8], key: &str) -> Result<Option<Event<'static>>, ParsingError> {
+ pub async fn maybe_open_start(
+ &mut self,
+ ns: &[u8],
+ key: &str,
+ ) -> Result<Option<Event<'static>>, ParsingError> {
match self.open_start(ns, key).await {
Ok(v) => Ok(Some(v)),
Err(ParsingError::Recoverable) => Ok(None),
@@ -289,9 +329,12 @@ impl<T: IRead> Reader<T> {
pub fn prev_attr(&self, attr: &str) -> Option<String> {
match &self.prev {
Event::Start(bs) | Event::Empty(bs) => match bs.try_get_attribute(attr) {
- Ok(Some(attr)) => attr.decode_and_unescape_value(&self.rdr).ok().map(|v| v.into_owned()),
+ Ok(Some(attr)) => attr
+ .decode_and_unescape_value(&self.rdr)
+ .ok()
+ .map(|v| v.into_owned()),
_ => None,
- }
+ },
_ => None,
}
}
@@ -303,7 +346,7 @@ impl<T: IRead> Reader<T> {
// Handle the empty case
if !self.parent_has_child() {
self.parents.pop();
- return self.next().await
+ return self.next().await;
}
// Handle the start/end case
@@ -311,11 +354,10 @@ impl<T: IRead> Reader<T> {
match self.peek() {
Event::End(_) => {
self.parents.pop();
- return self.next().await
- },
+ return self.next().await;
+ }
_ => self.skip().await?,
};
}
}
}
-
diff --git a/aero-proto/Cargo.toml b/aero-proto/Cargo.toml
index e9f28d1..b6f6336 100644
--- a/aero-proto/Cargo.toml
+++ b/aero-proto/Cargo.toml
@@ -35,3 +35,4 @@ smtp-message.workspace = true
smtp-server.workspace = true
tracing.workspace = true
quick-xml.workspace = true
+icalendar.workspace = true
diff --git a/aero-proto/src/dav/codec.rs b/aero-proto/src/dav/codec.rs
index 57c3808..a441e7e 100644
--- a/aero-proto/src/dav/codec.rs
+++ b/aero-proto/src/dav/codec.rs
@@ -1,26 +1,30 @@
use anyhow::{bail, Result};
-use hyper::{Request, Response, body::Bytes};
-use hyper::body::Incoming;
-use http_body_util::Full;
+use futures::sink::SinkExt;
use futures::stream::StreamExt;
use futures::stream::TryStreamExt;
+use http_body_util::combinators::UnsyncBoxBody;
+use http_body_util::BodyExt;
use http_body_util::BodyStream;
+use http_body_util::Full;
use http_body_util::StreamBody;
-use http_body_util::combinators::UnsyncBoxBody;
use hyper::body::Frame;
-use tokio_util::sync::PollSender;
+use hyper::body::Incoming;
+use hyper::{body::Bytes, Request, Response};
use std::io::{Error, ErrorKind};
-use futures::sink::SinkExt;
-use tokio_util::io::{SinkWriter, CopyToBytes};
-use http_body_util::BodyExt;
+use tokio_util::io::{CopyToBytes, SinkWriter};
+use tokio_util::sync::PollSender;
-use aero_dav::types as dav;
-use aero_dav::xml as dxml;
use super::controller::HttpResponse;
use super::node::PutPolicy;
+use aero_dav::types as dav;
+use aero_dav::xml as dxml;
pub(crate) fn depth(req: &Request<impl hyper::body::Body>) -> dav::Depth {
- match req.headers().get("Depth").map(hyper::header::HeaderValue::to_str) {
+ match req
+ .headers()
+ .get("Depth")
+ .map(hyper::header::HeaderValue::to_str)
+ {
Some(Ok("0")) => dav::Depth::Zero,
Some(Ok("1")) => dav::Depth::One,
Some(Ok("Infinity")) => dav::Depth::Infinity,
@@ -29,20 +33,28 @@ pub(crate) fn depth(req: &Request<impl hyper::body::Body>) -> dav::Depth {
}
pub(crate) fn put_policy(req: &Request<impl hyper::body::Body>) -> Result<PutPolicy> {
- if let Some(maybe_txt_etag) = req.headers().get("If-Match").map(hyper::header::HeaderValue::to_str) {
+ if let Some(maybe_txt_etag) = req
+ .headers()
+ .get("If-Match")
+ .map(hyper::header::HeaderValue::to_str)
+ {
let etag = maybe_txt_etag?;
let dquote_count = etag.chars().filter(|c| *c == '"').count();
if dquote_count != 2 {
bail!("Either If-Match value is invalid or it's not supported (only single etag is supported)");
}
- return Ok(PutPolicy::ReplaceEtag(etag.into()))
+ return Ok(PutPolicy::ReplaceEtag(etag.into()));
}
- if let Some(maybe_txt_etag) = req.headers().get("If-None-Match").map(hyper::header::HeaderValue::to_str) {
+ if let Some(maybe_txt_etag) = req
+ .headers()
+ .get("If-None-Match")
+ .map(hyper::header::HeaderValue::to_str)
+ {
let etag = maybe_txt_etag?;
if etag == "*" {
- return Ok(PutPolicy::CreateOnly)
+ return Ok(PutPolicy::CreateOnly);
}
bail!("Either If-None-Match value is invalid or it's not supported (only asterisk is supported)")
}
@@ -54,7 +66,10 @@ pub(crate) fn text_body(txt: &'static str) -> UnsyncBoxBody<Bytes, std::io::Erro
UnsyncBoxBody::new(Full::new(Bytes::from(txt)).map_err(|e| match e {}))
}
-pub(crate) fn serialize<T: dxml::QWrite + Send + 'static>(status_ok: hyper::StatusCode, elem: T) -> Result<HttpResponse> {
+pub(crate) fn serialize<T: dxml::QWrite + Send + 'static>(
+ status_ok: hyper::StatusCode,
+ elem: T,
+) -> Result<HttpResponse> {
let (tx, rx) = tokio::sync::mpsc::channel::<Bytes>(1);
// Build the writer
@@ -62,10 +77,21 @@ pub(crate) fn serialize<T: dxml::QWrite + Send + 'static>(status_ok: hyper::Stat
let sink = PollSender::new(tx).sink_map_err(|_| Error::from(ErrorKind::BrokenPipe));
let mut writer = SinkWriter::new(CopyToBytes::new(sink));
let q = quick_xml::writer::Writer::new_with_indent(&mut writer, b' ', 4);
- let ns_to_apply = vec![ ("xmlns:D".into(), "DAV:".into()), ("xmlns:C".into(), "urn:ietf:params:xml:ns:caldav".into()) ];
+ let ns_to_apply = vec![
+ ("xmlns:D".into(), "DAV:".into()),
+ ("xmlns:C".into(), "urn:ietf:params:xml:ns:caldav".into()),
+ ];
let mut qwriter = dxml::Writer { q, ns_to_apply };
- let decl = quick_xml::events::BytesDecl::from_start(quick_xml::events::BytesStart::from_content("xml version=\"1.0\" encoding=\"utf-8\"", 0));
- match qwriter.q.write_event_async(quick_xml::events::Event::Decl(decl)).await {
+ let decl =
+ quick_xml::events::BytesDecl::from_start(quick_xml::events::BytesStart::from_content(
+ "xml version=\"1.0\" encoding=\"utf-8\"",
+ 0,
+ ));
+ match qwriter
+ .q
+ .write_event_async(quick_xml::events::Event::Decl(decl))
+ .await
+ {
Ok(_) => (),
Err(e) => tracing::error!(err=?e, "unable to write XML declaration <?xml ... >"),
}
@@ -75,7 +101,6 @@ pub(crate) fn serialize<T: dxml::QWrite + Send + 'static>(status_ok: hyper::Stat
}
});
-
// Build the reader
let recv = tokio_stream::wrappers::ReceiverStream::new(rx);
let stream = StreamBody::new(recv.map(|v| Ok(Frame::data(v))));
@@ -89,7 +114,6 @@ pub(crate) fn serialize<T: dxml::QWrite + Send + 'static>(status_ok: hyper::Stat
Ok(response)
}
-
/// Deserialize a request body to an XML request
pub(crate) async fn deserialize<T: dxml::Node<T>>(req: Request<Incoming>) -> Result<T> {
let stream_of_frames = BodyStream::new(req.into_body());
@@ -97,7 +121,10 @@ pub(crate) async fn deserialize<T: dxml::Node<T>>(req: Request<Incoming>) -> Res
.map_ok(|frame| frame.into_data())
.map(|obj| match obj {
Ok(Ok(v)) => Ok(v),
- Ok(Err(_)) => Err(std::io::Error::new(std::io::ErrorKind::Other, "conversion error")),
+ Ok(Err(_)) => Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "conversion error",
+ )),
Err(err) => Err(std::io::Error::new(std::io::ErrorKind::Other, err)),
});
let async_read = tokio_util::io::StreamReader::new(stream_of_bytes);
diff --git a/aero-proto/src/dav/controller.rs b/aero-proto/src/dav/controller.rs
index 885828f..0bf7a7d 100644
--- a/aero-proto/src/dav/controller.rs
+++ b/aero-proto/src/dav/controller.rs
@@ -1,21 +1,21 @@
use anyhow::Result;
-use http_body_util::combinators::{UnsyncBoxBody, BoxBody};
-use hyper::body::Incoming;
-use hyper::{Request, Response, body::Bytes};
+use futures::stream::{StreamExt, TryStreamExt};
+use http_body_util::combinators::{BoxBody, UnsyncBoxBody};
use http_body_util::BodyStream;
use http_body_util::StreamBody;
use hyper::body::Frame;
-use futures::stream::{StreamExt, TryStreamExt};
+use hyper::body::Incoming;
+use hyper::{body::Bytes, Request, Response};
use aero_collections::user::User;
-use aero_dav::types as dav;
-use aero_dav::realization::All;
use aero_dav::caltypes as cal;
+use aero_dav::realization::All;
+use aero_dav::types as dav;
-use crate::dav::codec::{serialize, deserialize, depth, text_body};
+use crate::dav::codec;
+use crate::dav::codec::{depth, deserialize, serialize, text_body};
use crate::dav::node::{DavNode, PutPolicy};
use crate::dav::resource::RootNode;
-use crate::dav::codec;
pub(super) type ArcUser = std::sync::Arc<User>;
pub(super) type HttpResponse = Response<UnsyncBoxBody<Bytes, std::io::Error>>;
@@ -39,19 +39,22 @@ pub(crate) struct Controller {
req: Request<Incoming>,
}
impl Controller {
- pub(crate) async fn route(user: std::sync::Arc<User>, req: Request<Incoming>) -> Result<HttpResponse> {
+ pub(crate) async fn route(
+ user: std::sync::Arc<User>,
+ req: Request<Incoming>,
+ ) -> Result<HttpResponse> {
let path = req.uri().path().to_string();
let path_segments: Vec<_> = path.split("/").filter(|s| *s != "").collect();
let method = req.method().as_str().to_uppercase();
let can_create = matches!(method.as_str(), "PUT" | "MKCOL" | "MKCALENDAR");
- let node = match (RootNode {}).fetch(&user, &path_segments, can_create).await{
+ let node = match (RootNode {}).fetch(&user, &path_segments, can_create).await {
Ok(v) => v,
Err(e) => {
tracing::warn!(err=?e, "dav node fetch failed");
return Ok(Response::builder()
.status(404)
- .body(codec::text_body("Resource not found"))?)
+ .body(codec::text_body("Resource not found"))?);
}
};
@@ -80,7 +83,6 @@ impl Controller {
}
}
-
// --- Per-method functions ---
/// REPORT has been first described in the "Versioning Extension" of WebDAV
@@ -89,7 +91,7 @@ impl Controller {
/// Note: current implementation is not generic at all, it is heavily tied to CalDAV.
/// A rewrite would be required to make it more generic (with the extension system that has
/// been introduced in aero-dav)
- async fn report(self) -> Result<HttpResponse> {
+ async fn report(self) -> Result<HttpResponse> {
let status = hyper::StatusCode::from_u16(207)?;
let report = match deserialize::<cal::Report<All>>(self.req).await {
@@ -97,54 +99,75 @@ impl Controller {
Err(e) => {
tracing::error!(err=?e, "unable to decode REPORT body");
return Ok(Response::builder()
- .status(400)
- .body(text_body("Bad request"))?)
+ .status(400)
+ .body(text_body("Bad request"))?);
}
};
- // Multiget is really like a propfind where Depth: 0|1|Infinity is replaced by an arbitrary
- // list of URLs
- // @FIXME
- let multiget = match report {
- cal::Report::Multiget(m) => m,
- cal::Report::Query(q) => todo!(),
- cal::Report::FreeBusy(_) => return Ok(Response::builder()
- .status(501)
- .body(text_body("Not implemented"))?),
- };
-
- // Getting the list of nodes
+ // Internal representation that will handle processed request
let (mut ok_node, mut not_found) = (Vec::new(), Vec::new());
- for h in multiget.href.into_iter() {
- let maybe_collected_node = match Path::new(h.0.as_str()) {
- Ok(Path::Abs(p)) => RootNode{}.fetch(&self.user, p.as_slice(), false).await.or(Err(h)),
- Ok(Path::Rel(p)) => self.node.fetch(&self.user, p.as_slice(), false).await.or(Err(h)),
- Err(_) => Err(h),
- };
-
- match maybe_collected_node {
- Ok(v) => ok_node.push(v),
- Err(h) => not_found.push(h),
- };
- }
+ let calprop: Option<cal::CalendarSelector<All>>;
+
+ // Extracting request information
+ match report {
+ cal::Report::Multiget(m) => {
+ // Multiget is really like a propfind where Depth: 0|1|Infinity is replaced by an arbitrary
+ // list of URLs
+ // Getting the list of nodes
+ for h in m.href.into_iter() {
+ let maybe_collected_node = match Path::new(h.0.as_str()) {
+ Ok(Path::Abs(p)) => RootNode {}
+ .fetch(&self.user, p.as_slice(), false)
+ .await
+ .or(Err(h)),
+ Ok(Path::Rel(p)) => self
+ .node
+ .fetch(&self.user, p.as_slice(), false)
+ .await
+ .or(Err(h)),
+ Err(_) => Err(h),
+ };
+
+ match maybe_collected_node {
+ Ok(v) => ok_node.push(v),
+ Err(h) => not_found.push(h),
+ };
+ }
+ calprop = m.selector;
+ }
+ cal::Report::Query(q) => {
+ calprop = q.selector;
+ ok_node = apply_filter(&self.user, self.node.children(&self.user).await, q.filter)
+ .try_collect()
+ .await?;
+ }
+ cal::Report::FreeBusy(_) => {
+ return Ok(Response::builder()
+ .status(501)
+ .body(text_body("Not implemented"))?)
+ }
+ };
// Getting props
- let props = match multiget.selector {
+ let props = match calprop {
None | Some(cal::CalendarSelector::AllProp) => Some(dav::PropName(ALLPROP.to_vec())),
Some(cal::CalendarSelector::PropName) => None,
Some(cal::CalendarSelector::Prop(inner)) => Some(inner),
};
- serialize(status, Self::multistatus(&self.user, ok_node, not_found, props).await)
+ serialize(
+ status,
+ Self::multistatus(&self.user, ok_node, not_found, props).await,
+ )
}
/// PROPFIND is the standard way to fetch WebDAV properties
- async fn propfind(self) -> Result<HttpResponse> {
+ async fn propfind(self) -> Result<HttpResponse> {
let depth = depth(&self.req);
if matches!(depth, dav::Depth::Infinity) {
return Ok(Response::builder()
.status(501)
- .body(text_body("Depth: Infinity not implemented"))?)
+ .body(text_body("Depth: Infinity not implemented"))?);
}
let status = hyper::StatusCode::from_u16(207)?;
@@ -153,7 +176,9 @@ impl Controller {
// request body MUST be treated as if it were an 'allprop' request.
// @FIXME here we handle any invalid data as an allprop, an empty request is thus correctly
// handled, but corrupted requests are also silently handled as allprop.
- let propfind = deserialize::<dav::PropFind<All>>(self.req).await.unwrap_or_else(|_| dav::PropFind::<All>::AllProp(None));
+ let propfind = deserialize::<dav::PropFind<All>>(self.req)
+ .await
+ .unwrap_or_else(|_| dav::PropFind::<All>::AllProp(None));
tracing::debug!(recv=?propfind, "inferred propfind request");
// Collect nodes as PROPFIND is not limited to the targeted node
@@ -170,29 +195,36 @@ impl Controller {
dav::PropFind::AllProp(Some(dav::Include(mut include))) => {
include.extend_from_slice(&ALLPROP);
Some(dav::PropName(include))
- },
+ }
dav::PropFind::Prop(inner) => Some(inner),
};
// Not Found is currently impossible considering the way we designed this function
let not_found = vec![];
- serialize(status, Self::multistatus(&self.user, nodes, not_found, propname).await)
+ serialize(
+ status,
+ Self::multistatus(&self.user, nodes, not_found, propname).await,
+ )
}
- async fn put(self) -> Result<HttpResponse> {
+ async fn put(self) -> Result<HttpResponse> {
let put_policy = codec::put_policy(&self.req)?;
let stream_of_frames = BodyStream::new(self.req.into_body());
let stream_of_bytes = stream_of_frames
- .map_ok(|frame| frame.into_data())
- .map(|obj| match obj {
- Ok(Ok(v)) => Ok(v),
- Ok(Err(_)) => Err(std::io::Error::new(std::io::ErrorKind::Other, "conversion error")),
- Err(err) => Err(std::io::Error::new(std::io::ErrorKind::Other, err)),
- }).boxed();
+ .map_ok(|frame| frame.into_data())
+ .map(|obj| match obj {
+ Ok(Ok(v)) => Ok(v),
+ Ok(Err(_)) => Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "conversion error",
+ )),
+ Err(err) => Err(std::io::Error::new(std::io::ErrorKind::Other, err)),
+ })
+ .boxed();
let etag = self.node.put(put_policy, stream_of_bytes).await?;
-
+
let response = Response::builder()
.status(201)
.header("ETag", etag)
@@ -202,7 +234,7 @@ impl Controller {
Ok(response)
}
- async fn get(self) -> Result<HttpResponse> {
+ async fn get(self) -> Result<HttpResponse> {
let stream_body = StreamBody::new(self.node.content().map_ok(|v| Frame::data(v)));
let boxed_body = UnsyncBoxBody::new(stream_body);
@@ -227,17 +259,33 @@ impl Controller {
// --- Common utility functions ---
/// Build a multistatus response from a list of DavNodes
- async fn multistatus(user: &ArcUser, nodes: Vec<Box<dyn DavNode>>, not_found: Vec<dav::Href>, props: Option<dav::PropName<All>>) -> dav::Multistatus<All> {
+ async fn multistatus(
+ user: &ArcUser,
+ nodes: Vec<Box<dyn DavNode>>,
+ not_found: Vec<dav::Href>,
+ props: Option<dav::PropName<All>>,
+ ) -> dav::Multistatus<All> {
// Collect properties on existing objects
let mut responses: Vec<dav::Response<All>> = match props {
- Some(props) => futures::stream::iter(nodes).then(|n| n.response_props(user, props.clone())).collect().await,
- None => nodes.into_iter().map(|n| n.response_propname(user)).collect(),
+ Some(props) => {
+ futures::stream::iter(nodes)
+ .then(|n| n.response_props(user, props.clone()))
+ .collect()
+ .await
+ }
+ None => nodes
+ .into_iter()
+ .map(|n| n.response_propname(user))
+ .collect(),
};
// Register not found objects only if relevant
if !not_found.is_empty() {
responses.push(dav::Response {
- status_or_propstat: dav::StatusOrPropstat::Status(not_found, dav::Status(hyper::StatusCode::NOT_FOUND)),
+ status_or_propstat: dav::StatusOrPropstat::Status(
+ not_found,
+ dav::Status(hyper::StatusCode::NOT_FOUND),
+ ),
error: None,
location: None,
responsedescription: None,
@@ -252,7 +300,6 @@ impl Controller {
}
}
-
/// Path is a voluntarily feature limited
/// compared to the expressiveness of a UNIX path
/// For example getting parent with ../ is not supported, scheme is not supported, etc.
@@ -271,8 +318,39 @@ impl<'a> Path<'a> {
let path_segments: Vec<_> = path.split("/").filter(|s| *s != "" && *s != ".").collect();
if path.starts_with("/") {
- return Ok(Path::Abs(path_segments))
+ return Ok(Path::Abs(path_segments));
}
Ok(Path::Rel(path_segments))
}
}
+
+//@FIXME move somewhere else
+//@FIXME naive implementation, must be refactored later
+use futures::stream::Stream;
+use icalendar;
+fn apply_filter(
+ user: &ArcUser,
+ nodes: Vec<Box<dyn DavNode>>,
+ filter: cal::Filter,
+) -> impl Stream<Item = std::result::Result<Box<dyn DavNode>, std::io::Error>> {
+ futures::stream::iter(nodes).filter_map(|single_node| async move {
+ // Get ICS
+ let chunks: Vec<_> = match single_node.content().try_collect().await {
+ Ok(v) => v,
+ Err(e) => return Some(Err(e)),
+ };
+ let raw_ics = chunks.iter().fold(String::new(), |mut acc, single_chunk| {
+ let str_fragment = std::str::from_utf8(single_chunk.as_ref());
+ acc.extend(str_fragment);
+ acc
+ });
+
+ // Parse ICS
+ let ics = icalendar::parser::read_calendar(&raw_ics).unwrap();
+
+ // Do checks
+
+ // Object has been kept
+ Some(Ok(single_node))
+ })
+}
diff --git a/aero-proto/src/dav/middleware.rs b/aero-proto/src/dav/middleware.rs
index e19ce14..8964699 100644
--- a/aero-proto/src/dav/middleware.rs
+++ b/aero-proto/src/dav/middleware.rs
@@ -1,10 +1,10 @@
use anyhow::{anyhow, Result};
use base64::Engine;
-use hyper::{Request, Response};
use hyper::body::Incoming;
+use hyper::{Request, Response};
-use aero_user::login::ArcLoginProvider;
use aero_collections::user::User;
+use aero_user::login::ArcLoginProvider;
use super::codec::text_body;
use super::controller::HttpResponse;
@@ -13,7 +13,7 @@ type ArcUser = std::sync::Arc<User>;
pub(super) async fn auth<'a>(
login: ArcLoginProvider,
- req: Request<Incoming>,
+ req: Request<Incoming>,
next: impl Fn(ArcUser, Request<Incoming>) -> futures::future::BoxFuture<'a, Result<HttpResponse>>,
) -> Result<HttpResponse> {
let auth_val = match req.headers().get(hyper::header::AUTHORIZATION) {
@@ -23,8 +23,8 @@ pub(super) async fn auth<'a>(
return Ok(Response::builder()
.status(401)
.header("WWW-Authenticate", "Basic realm=\"Aerogramme\"")
- .body(text_body("Missing Authorization field"))?)
- },
+ .body(text_body("Missing Authorization field"))?);
+ }
};
let b64_creds_maybe_padded = match auth_val.split_once(" ") {
@@ -33,8 +33,8 @@ pub(super) async fn auth<'a>(
tracing::info!("Unsupported authorization field");
return Ok(Response::builder()
.status(400)
- .body(text_body("Unsupported Authorization field"))?)
- },
+ .body(text_body("Unsupported Authorization field"))?);
+ }
};
// base64urlencoded may have trailing equals, base64urlsafe has not
@@ -44,22 +44,22 @@ pub(super) async fn auth<'a>(
// Decode base64
let creds = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64_creds_clean)?;
let str_creds = std::str::from_utf8(&creds)?;
-
+
// Split username and password
- let (username, password) = str_creds
- .split_once(':')
- .ok_or(anyhow!("Missing colon in Authorization, can't split decoded value into a username/password pair"))?;
+ let (username, password) = str_creds.split_once(':').ok_or(anyhow!(
+ "Missing colon in Authorization, can't split decoded value into a username/password pair"
+ ))?;
// Call login provider
let creds = match login.login(username, password).await {
Ok(c) => c,
Err(_) => {
- tracing::info!(user=username, "Wrong credentials");
+ tracing::info!(user = username, "Wrong credentials");
return Ok(Response::builder()
.status(401)
.header("WWW-Authenticate", "Basic realm=\"Aerogramme\"")
- .body(text_body("Wrong credentials"))?)
- },
+ .body(text_body("Wrong credentials"))?);
+ }
};
// Build a user
diff --git a/aero-proto/src/dav/mod.rs b/aero-proto/src/dav/mod.rs
index de2e690..43de3a5 100644
--- a/aero-proto/src/dav/mod.rs
+++ b/aero-proto/src/dav/mod.rs
@@ -1,6 +1,6 @@
-mod middleware;
-mod controller;
mod codec;
+mod controller;
+mod middleware;
mod node;
mod resource;
@@ -8,19 +8,19 @@ use std::net::SocketAddr;
use std::sync::Arc;
use anyhow::Result;
+use futures::future::FutureExt;
+use futures::stream::{FuturesUnordered, StreamExt};
+use hyper::rt::{Read, Write};
+use hyper::server::conn::http1 as http;
use hyper::service::service_fn;
use hyper::{Request, Response};
-use hyper::server::conn::http1 as http;
-use hyper::rt::{Read, Write};
use hyper_util::rt::TokioIo;
-use futures::stream::{FuturesUnordered, StreamExt};
+use rustls_pemfile::{certs, private_key};
+use tokio::io::{AsyncRead, AsyncWrite};
use tokio::net::TcpListener;
+use tokio::net::TcpStream;
use tokio::sync::watch;
use tokio_rustls::TlsAcceptor;
-use tokio::net::TcpStream;
-use futures::future::FutureExt;
-use tokio::io::{AsyncRead, AsyncWrite};
-use rustls_pemfile::{certs, private_key};
use aero_user::config::{DavConfig, DavUnsecureConfig};
use aero_user::login::ArcLoginProvider;
@@ -90,7 +90,7 @@ impl Server {
Ok(v) => v,
Err(e) => {
tracing::error!(err=?e, "TLS acceptor failed");
- continue
+ continue;
}
};
@@ -100,21 +100,31 @@ impl Server {
//abitrarily bound
//@FIXME replace with a handler supporting http2 and TLS
- match http::Builder::new().serve_connection(stream, service_fn(|req: Request<hyper::body::Incoming>| {
- let login = login.clone();
- tracing::info!("{:?} {:?}", req.method(), req.uri());
- async {
- match middleware::auth(login, req, |user, request| async { Controller::route(user, request).await }.boxed()).await {
- Ok(v) => Ok(v),
- Err(e) => {
- tracing::error!(err=?e, "internal error");
- Response::builder()
- .status(500)
- .body(codec::text_body("Internal error"))
- },
- }
- }
- })).await {
+ match http::Builder::new()
+ .serve_connection(
+ stream,
+ service_fn(|req: Request<hyper::body::Incoming>| {
+ let login = login.clone();
+ tracing::info!("{:?} {:?}", req.method(), req.uri());
+ async {
+ match middleware::auth(login, req, |user, request| {
+ async { Controller::route(user, request).await }.boxed()
+ })
+ .await
+ {
+ Ok(v) => Ok(v),
+ Err(e) => {
+ tracing::error!(err=?e, "internal error");
+ Response::builder()
+ .status(500)
+ .body(codec::text_body("Internal error"))
+ }
+ }
+ }
+ }),
+ )
+ .await
+ {
Err(e) => tracing::warn!(err=?e, "connection failed"),
Ok(()) => tracing::trace!("connection terminated with success"),
}
@@ -149,7 +159,6 @@ impl Server {
// </D:prop>
// </D:propfind>
-
// <D:propfind xmlns:D='DAV:' xmlns:A='http://apple.com/ns/ical/' xmlns:C='urn:ietf:params:xml:ns:caldav'>
// <D:prop>
// <D:resourcetype/>
diff --git a/aero-proto/src/dav/node.rs b/aero-proto/src/dav/node.rs
index d246280..877342a 100644
--- a/aero-proto/src/dav/node.rs
+++ b/aero-proto/src/dav/node.rs
@@ -1,16 +1,17 @@
use anyhow::Result;
-use futures::stream::{BoxStream, StreamExt};
use futures::future::{BoxFuture, FutureExt};
+use futures::stream::{BoxStream, StreamExt};
use hyper::body::Bytes;
-use aero_dav::types as dav;
-use aero_dav::realization::All;
use aero_collections::davdag::Etag;
+use aero_dav::realization::All;
+use aero_dav::types as dav;
use super::controller::ArcUser;
pub(crate) type Content<'a> = BoxStream<'a, std::result::Result<Bytes, std::io::Error>>;
-pub(crate) type PropertyStream<'a> = BoxStream<'a, std::result::Result<dav::Property<All>, dav::PropertyRequest<All>>>;
+pub(crate) type PropertyStream<'a> =
+ BoxStream<'a, std::result::Result<dav::Property<All>, dav::PropertyRequest<All>>>;
pub(crate) enum PutPolicy {
OverwriteAll,
@@ -25,7 +26,12 @@ pub(crate) trait DavNode: Send {
/// This node direct children
fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec<Box<dyn DavNode>>>;
/// Recursively fetch a child (progress inside the filesystem hierarchy)
- fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str], create: bool) -> BoxFuture<'a, Result<Box<dyn DavNode>>>;
+ fn fetch<'a>(
+ &self,
+ user: &'a ArcUser,
+ path: &'a [&str],
+ create: bool,
+ ) -> BoxFuture<'a, Result<Box<dyn DavNode>>>;
// node properties
/// Get the path
@@ -36,13 +42,17 @@ pub(crate) trait DavNode: Send {
fn properties(&self, user: &ArcUser, prop: dav::PropName<All>) -> PropertyStream<'static>;
//fn properties(&self, user: &ArcUser, prop: dav::PropName<All>) -> Vec<dav::AnyProperty<All>>;
/// Put an element (create or update)
- fn put<'a>(&'a self, policy: PutPolicy, stream: Content<'a>) -> BoxFuture<'a, std::result::Result<Etag, std::io::Error>>;
+ fn put<'a>(
+ &'a self,
+ policy: PutPolicy,
+ stream: Content<'a>,
+ ) -> BoxFuture<'a, std::result::Result<Etag, std::io::Error>>;
/// Content type of the element
fn content_type(&self) -> &str;
/// Get ETag
fn etag(&self) -> BoxFuture<Option<Etag>>;
/// Get content
- fn content(&self) -> Content<'static>;
+ fn content<'a>(&self) -> Content<'a>;
/// Delete
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>>;
@@ -52,24 +62,32 @@ pub(crate) trait DavNode: Send {
fn response_propname(&self, user: &ArcUser) -> dav::Response<All> {
dav::Response {
status_or_propstat: dav::StatusOrPropstat::PropStat(
- dav::Href(self.path(user)),
- vec![
- dav::PropStat {
- status: dav::Status(hyper::StatusCode::OK),
- prop: dav::AnyProp(self.supported_properties(user).0.into_iter().map(dav::AnyProperty::Request).collect()),
- error: None,
- responsedescription: None,
- }
- ],
+ dav::Href(self.path(user)),
+ vec![dav::PropStat {
+ status: dav::Status(hyper::StatusCode::OK),
+ prop: dav::AnyProp(
+ self.supported_properties(user)
+ .0
+ .into_iter()
+ .map(dav::AnyProperty::Request)
+ .collect(),
+ ),
+ error: None,
+ responsedescription: None,
+ }],
),
error: None,
location: None,
- responsedescription: None
+ responsedescription: None,
}
}
/// Utility function to get a prop response from a node & a list of propname
- fn response_props(&self, user: &ArcUser, props: dav::PropName<All>) -> BoxFuture<'static, dav::Response<All>> {
+ fn response_props(
+ &self,
+ user: &ArcUser,
+ props: dav::PropName<All>,
+ ) -> BoxFuture<'static, dav::Response<All>> {
//@FIXME we should make the DAV parsed object a stream...
let mut result_stream = self.properties(user, props);
let path = self.path(user);
@@ -87,8 +105,8 @@ pub(crate) trait DavNode: Send {
// If at least one property has been found on this object, adding a HTTP 200 propstat to
// the response
if !found.is_empty() {
- prop_desc.push(dav::PropStat {
- status: dav::Status(hyper::StatusCode::OK),
+ prop_desc.push(dav::PropStat {
+ status: dav::Status(hyper::StatusCode::OK),
prop: dav::AnyProp(found),
error: None,
responsedescription: None,
@@ -98,8 +116,8 @@ pub(crate) trait DavNode: Send {
// If at least one property can't be found on this object, adding a HTTP 404 propstat to
// the response
if !not_found.is_empty() {
- prop_desc.push(dav::PropStat {
- status: dav::Status(hyper::StatusCode::NOT_FOUND),
+ prop_desc.push(dav::PropStat {
+ status: dav::Status(hyper::StatusCode::NOT_FOUND),
prop: dav::AnyProp(not_found),
error: None,
responsedescription: None,
@@ -111,9 +129,9 @@ pub(crate) trait DavNode: Send {
status_or_propstat: dav::StatusOrPropstat::PropStat(dav::Href(path), prop_desc),
error: None,
location: None,
- responsedescription: None
+ responsedescription: None,
}
- }.boxed()
+ }
+ .boxed()
}
}
-
diff --git a/aero-proto/src/dav/resource.rs b/aero-proto/src/dav/resource.rs
index 944c6c8..d65ce38 100644
--- a/aero-proto/src/dav/resource.rs
+++ b/aero-proto/src/dav/resource.rs
@@ -2,23 +2,32 @@ use std::sync::Arc;
type ArcUser = std::sync::Arc<User>;
use anyhow::{anyhow, Result};
-use futures::stream::{TryStreamExt, StreamExt};
use futures::io::AsyncReadExt;
+use futures::stream::{StreamExt, TryStreamExt};
use futures::{future::BoxFuture, future::FutureExt};
-use aero_collections::{user::User, calendar::Calendar, davdag::{BlobId, Etag}};
-use aero_dav::types as dav;
-use aero_dav::caltypes as cal;
+use aero_collections::{
+ calendar::Calendar,
+ davdag::{BlobId, Etag},
+ user::User,
+};
use aero_dav::acltypes as acl;
-use aero_dav::realization::{All, self as all};
+use aero_dav::caltypes as cal;
+use aero_dav::realization::{self as all, All};
+use aero_dav::types as dav;
-use crate::dav::node::{DavNode, PutPolicy, Content};
use super::node::PropertyStream;
+use crate::dav::node::{Content, DavNode, PutPolicy};
#[derive(Clone)]
pub(crate) struct RootNode {}
impl DavNode for RootNode {
- fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str], create: bool) -> BoxFuture<'a, Result<Box<dyn DavNode>>> {
+ fn fetch<'a>(
+ &self,
+ user: &'a ArcUser,
+ path: &'a [&str],
+ create: bool,
+ ) -> BoxFuture<'a, Result<Box<dyn DavNode>>> {
if path.len() == 0 {
let this = self.clone();
return async { Ok(Box::new(this) as Box<dyn DavNode>) }.boxed();
@@ -34,7 +43,7 @@ impl DavNode for RootNode {
}
fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec<Box<dyn DavNode>>> {
- async { vec![Box::new(HomeNode { }) as Box<dyn DavNode>] }.boxed()
+ async { vec![Box::new(HomeNode {}) as Box<dyn DavNode>] }.boxed()
}
fn path(&self, user: &ArcUser) -> String {
@@ -46,33 +55,53 @@ impl DavNode for RootNode {
dav::PropertyRequest::DisplayName,
dav::PropertyRequest::ResourceType,
dav::PropertyRequest::GetContentType,
- dav::PropertyRequest::Extension(all::PropertyRequest::Acl(acl::PropertyRequest::CurrentUserPrincipal)),
+ dav::PropertyRequest::Extension(all::PropertyRequest::Acl(
+ acl::PropertyRequest::CurrentUserPrincipal,
+ )),
])
}
fn properties(&self, user: &ArcUser, prop: dav::PropName<All>) -> PropertyStream<'static> {
let user = user.clone();
- futures::stream::iter(prop.0).map(move |n| {
- let prop = match n {
- dav::PropertyRequest::DisplayName => dav::Property::DisplayName("DAV Root".to_string()),
- dav::PropertyRequest::ResourceType => dav::Property::ResourceType(vec![
- dav::ResourceType::Collection,
- ]),
- dav::PropertyRequest::GetContentType => dav::Property::GetContentType("httpd/unix-directory".into()),
- dav::PropertyRequest::Extension(all::PropertyRequest::Acl(acl::PropertyRequest::CurrentUserPrincipal)) =>
- dav::Property::Extension(all::Property::Acl(acl::Property::CurrentUserPrincipal(acl::User::Authenticated(dav::Href(HomeNode{}.path(&user)))))),
- v => return Err(v),
- };
- Ok(prop)
- }).boxed()
+ futures::stream::iter(prop.0)
+ .map(move |n| {
+ let prop = match n {
+ dav::PropertyRequest::DisplayName => {
+ dav::Property::DisplayName("DAV Root".to_string())
+ }
+ dav::PropertyRequest::ResourceType => {
+ dav::Property::ResourceType(vec![dav::ResourceType::Collection])
+ }
+ dav::PropertyRequest::GetContentType => {
+ dav::Property::GetContentType("httpd/unix-directory".into())
+ }
+ dav::PropertyRequest::Extension(all::PropertyRequest::Acl(
+ acl::PropertyRequest::CurrentUserPrincipal,
+ )) => dav::Property::Extension(all::Property::Acl(
+ acl::Property::CurrentUserPrincipal(acl::User::Authenticated(dav::Href(
+ HomeNode {}.path(&user),
+ ))),
+ )),
+ v => return Err(v),
+ };
+ Ok(prop)
+ })
+ .boxed()
}
- fn put<'a>(&'a self, _policy: PutPolicy, stream: Content<'a>) -> BoxFuture<'a, std::result::Result<Etag, std::io::Error>> {
+ fn put<'a>(
+ &'a self,
+ _policy: PutPolicy,
+ stream: Content<'a>,
+ ) -> BoxFuture<'a, std::result::Result<Etag, std::io::Error>> {
futures::future::err(std::io::Error::from(std::io::ErrorKind::Unsupported)).boxed()
}
- fn content(&self) -> Content<'static> {
- futures::stream::once(futures::future::err(std::io::Error::from(std::io::ErrorKind::Unsupported))).boxed()
+ fn content<'a>(&self) -> Content<'a> {
+ futures::stream::once(futures::future::err(std::io::Error::from(
+ std::io::ErrorKind::Unsupported,
+ )))
+ .boxed()
}
fn content_type(&self) -> &str {
@@ -91,29 +120,37 @@ impl DavNode for RootNode {
#[derive(Clone)]
pub(crate) struct HomeNode {}
impl DavNode for HomeNode {
- fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str], create: bool) -> BoxFuture<'a, Result<Box<dyn DavNode>>> {
+ fn fetch<'a>(
+ &self,
+ user: &'a ArcUser,
+ path: &'a [&str],
+ create: bool,
+ ) -> BoxFuture<'a, Result<Box<dyn DavNode>>> {
if path.len() == 0 {
let node = Box::new(self.clone()) as Box<dyn DavNode>;
- return async { Ok(node) }.boxed()
+ return async { Ok(node) }.boxed();
}
if path[0] == "calendar" {
return async move {
let child = Box::new(CalendarListNode::new(user).await?);
child.fetch(user, &path[1..], create).await
- }.boxed();
+ }
+ .boxed();
}
-
+
//@NOTE: we can't create a node at this level
async { Err(anyhow!("Not found")) }.boxed()
}
fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec<Box<dyn DavNode>>> {
- async {
- CalendarListNode::new(user).await
+ async {
+ CalendarListNode::new(user)
+ .await
.map(|c| vec![Box::new(c) as Box<dyn DavNode>])
- .unwrap_or(vec![])
- }.boxed()
+ .unwrap_or(vec![])
+ }
+ .boxed()
}
fn path(&self, user: &ArcUser) -> String {
@@ -125,38 +162,58 @@ impl DavNode for HomeNode {
dav::PropertyRequest::DisplayName,
dav::PropertyRequest::ResourceType,
dav::PropertyRequest::GetContentType,
- dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::CalendarHomeSet)),
+ dav::PropertyRequest::Extension(all::PropertyRequest::Cal(
+ cal::PropertyRequest::CalendarHomeSet,
+ )),
])
}
fn properties(&self, user: &ArcUser, prop: dav::PropName<All>) -> PropertyStream<'static> {
let user = user.clone();
- futures::stream::iter(prop.0).map(move |n| {
- let prop = match n {
- dav::PropertyRequest::DisplayName => dav::Property::DisplayName(format!("{} home", user.username)),
- dav::PropertyRequest::ResourceType => dav::Property::ResourceType(vec![
- dav::ResourceType::Collection,
- dav::ResourceType::Extension(all::ResourceType::Acl(acl::ResourceType::Principal)),
- ]),
- dav::PropertyRequest::GetContentType => dav::Property::GetContentType("httpd/unix-directory".into()),
- dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::CalendarHomeSet)) =>
- dav::Property::Extension(all::Property::Cal(cal::Property::CalendarHomeSet(dav::Href(
- //@FIXME we are hardcoding the calendar path, instead we would want to use
- //objects
- format!("/{}/calendar/", user.username)
- )))),
- v => return Err(v),
- };
- Ok(prop)
- }).boxed()
+ futures::stream::iter(prop.0)
+ .map(move |n| {
+ let prop = match n {
+ dav::PropertyRequest::DisplayName => {
+ dav::Property::DisplayName(format!("{} home", user.username))
+ }
+ dav::PropertyRequest::ResourceType => dav::Property::ResourceType(vec![
+ dav::ResourceType::Collection,
+ dav::ResourceType::Extension(all::ResourceType::Acl(
+ acl::ResourceType::Principal,
+ )),
+ ]),
+ dav::PropertyRequest::GetContentType => {
+ dav::Property::GetContentType("httpd/unix-directory".into())
+ }
+ dav::PropertyRequest::Extension(all::PropertyRequest::Cal(
+ cal::PropertyRequest::CalendarHomeSet,
+ )) => dav::Property::Extension(all::Property::Cal(
+ cal::Property::CalendarHomeSet(dav::Href(
+ //@FIXME we are hardcoding the calendar path, instead we would want to use
+ //objects
+ format!("/{}/calendar/", user.username),
+ )),
+ )),
+ v => return Err(v),
+ };
+ Ok(prop)
+ })
+ .boxed()
}
- fn put<'a>(&'a self, _policy: PutPolicy, stream: Content<'a>) -> BoxFuture<'a, std::result::Result<Etag, std::io::Error>> {
+ fn put<'a>(
+ &'a self,
+ _policy: PutPolicy,
+ stream: Content<'a>,
+ ) -> BoxFuture<'a, std::result::Result<Etag, std::io::Error>> {
futures::future::err(std::io::Error::from(std::io::ErrorKind::Unsupported)).boxed()
}
-
- fn content(&self) -> Content<'static> {
- futures::stream::once(futures::future::err(std::io::Error::from(std::io::ErrorKind::Unsupported))).boxed()
+
+ fn content<'a>(&self) -> Content<'a> {
+ futures::stream::once(futures::future::err(std::io::Error::from(
+ std::io::ErrorKind::Unsupported,
+ )))
+ .boxed()
}
fn content_type(&self) -> &str {
@@ -183,7 +240,12 @@ impl CalendarListNode {
}
}
impl DavNode for CalendarListNode {
- fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str], create: bool) -> BoxFuture<'a, Result<Box<dyn DavNode>>> {
+ fn fetch<'a>(
+ &self,
+ user: &'a ArcUser,
+ path: &'a [&str],
+ create: bool,
+ ) -> BoxFuture<'a, Result<Box<dyn DavNode>>> {
if path.len() == 0 {
let node = Box::new(self.clone()) as Box<dyn DavNode>;
return async { Ok(node) }.boxed();
@@ -191,13 +253,18 @@ impl DavNode for CalendarListNode {
async move {
//@FIXME: we should create a node if the open returns a "not found".
- let cal = user.calendars.open(user, path[0]).await?.ok_or(anyhow!("Not found"))?;
- let child = Box::new(CalendarNode {
+ let cal = user
+ .calendars
+ .open(user, path[0])
+ .await?
+ .ok_or(anyhow!("Not found"))?;
+ let child = Box::new(CalendarNode {
col: cal,
- calname: path[0].to_string()
+ calname: path[0].to_string(),
});
child.fetch(user, &path[1..], create).await
- }.boxed()
+ }
+ .boxed()
}
fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec<Box<dyn DavNode>>> {
@@ -206,18 +273,23 @@ impl DavNode for CalendarListNode {
//@FIXME maybe we want to be lazy here?!
futures::stream::iter(list.iter())
.filter_map(|name| async move {
- user.calendars.open(user, name).await
+ user.calendars
+ .open(user, name)
+ .await
.ok()
.flatten()
.map(|v| (name, v))
})
- .map(|(name, cal)| Box::new(CalendarNode {
- col: cal,
- calname: name.to_string(),
- }) as Box<dyn DavNode>)
+ .map(|(name, cal)| {
+ Box::new(CalendarNode {
+ col: cal,
+ calname: name.to_string(),
+ }) as Box<dyn DavNode>
+ })
.collect::<Vec<Box<dyn DavNode>>>()
.await
- }.boxed()
+ }
+ .boxed()
}
fn path(&self, user: &ArcUser) -> String {
@@ -234,23 +306,38 @@ impl DavNode for CalendarListNode {
fn properties(&self, user: &ArcUser, prop: dav::PropName<All>) -> PropertyStream<'static> {
let user = user.clone();
- futures::stream::iter(prop.0).map(move |n| {
- let prop = match n {
- dav::PropertyRequest::DisplayName => dav::Property::DisplayName(format!("{} calendars", user.username)),
- dav::PropertyRequest::ResourceType => dav::Property::ResourceType(vec![dav::ResourceType::Collection]),
- dav::PropertyRequest::GetContentType => dav::Property::GetContentType("httpd/unix-directory".into()),
- v => return Err(v),
- };
- Ok(prop)
- }).boxed()
+ futures::stream::iter(prop.0)
+ .map(move |n| {
+ let prop = match n {
+ dav::PropertyRequest::DisplayName => {
+ dav::Property::DisplayName(format!("{} calendars", user.username))
+ }
+ dav::PropertyRequest::ResourceType => {
+ dav::Property::ResourceType(vec![dav::ResourceType::Collection])
+ }
+ dav::PropertyRequest::GetContentType => {
+ dav::Property::GetContentType("httpd/unix-directory".into())
+ }
+ v => return Err(v),
+ };
+ Ok(prop)
+ })
+ .boxed()
}
- fn put<'a>(&'a self, _policy: PutPolicy, stream: Content<'a>) -> BoxFuture<'a, std::result::Result<Etag, std::io::Error>> {
+ fn put<'a>(
+ &'a self,
+ _policy: PutPolicy,
+ stream: Content<'a>,
+ ) -> BoxFuture<'a, std::result::Result<Etag, std::io::Error>> {
futures::future::err(std::io::Error::from(std::io::ErrorKind::Unsupported)).boxed()
}
- fn content(&self) -> Content<'static> {
- futures::stream::once(futures::future::err(std::io::Error::from(std::io::ErrorKind::Unsupported))).boxed()
+ fn content<'a>(&self) -> Content<'a> {
+ futures::stream::once(futures::future::err(std::io::Error::from(
+ std::io::ErrorKind::Unsupported,
+ )))
+ .boxed()
}
fn content_type(&self) -> &str {
@@ -272,17 +359,22 @@ pub(crate) struct CalendarNode {
calname: String,
}
impl DavNode for CalendarNode {
- fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str], create: bool) -> BoxFuture<'a, Result<Box<dyn DavNode>>> {
+ fn fetch<'a>(
+ &self,
+ user: &'a ArcUser,
+ path: &'a [&str],
+ create: bool,
+ ) -> BoxFuture<'a, Result<Box<dyn DavNode>>> {
if path.len() == 0 {
let node = Box::new(self.clone()) as Box<dyn DavNode>;
- return async { Ok(node) }.boxed()
+ return async { Ok(node) }.boxed();
}
let col = self.col.clone();
let calname = self.calname.clone();
async move {
match (col.dag().await.idx_by_filename.get(path[0]), create) {
- (Some(blob_id), _) => {
+ (Some(blob_id), _) => {
let child = Box::new(EventNode {
col: col.clone(),
calname,
@@ -290,7 +382,7 @@ impl DavNode for CalendarNode {
blob_id: *blob_id,
});
child.fetch(user, &path[1..], create).await
- },
+ }
(None, true) => {
let child = Box::new(CreateEventNode {
col: col.clone(),
@@ -298,11 +390,11 @@ impl DavNode for CalendarNode {
filename: path[0].to_string(),
});
child.fetch(user, &path[1..], create).await
- },
+ }
_ => Err(anyhow!("Not found")),
}
-
- }.boxed()
+ }
+ .boxed()
}
fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec<Box<dyn DavNode>>> {
@@ -310,15 +402,21 @@ impl DavNode for CalendarNode {
let calname = self.calname.clone();
async move {
- col.dag().await.idx_by_filename.iter().map(|(filename, blob_id)| {
- Box::new(EventNode {
- col: col.clone(),
- calname: calname.clone(),
- filename: filename.to_string(),
- blob_id: *blob_id,
- }) as Box<dyn DavNode>
- }).collect()
- }.boxed()
+ col.dag()
+ .await
+ .idx_by_filename
+ .iter()
+ .map(|(filename, blob_id)| {
+ Box::new(EventNode {
+ col: col.clone(),
+ calname: calname.clone(),
+ filename: filename.to_string(),
+ blob_id: *blob_id,
+ }) as Box<dyn DavNode>
+ })
+ .collect()
+ }
+ .boxed()
}
fn path(&self, user: &ArcUser) -> String {
@@ -330,38 +428,58 @@ impl DavNode for CalendarNode {
dav::PropertyRequest::DisplayName,
dav::PropertyRequest::ResourceType,
dav::PropertyRequest::GetContentType,
- dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::SupportedCalendarComponentSet)),
+ dav::PropertyRequest::Extension(all::PropertyRequest::Cal(
+ cal::PropertyRequest::SupportedCalendarComponentSet,
+ )),
])
}
fn properties(&self, _user: &ArcUser, prop: dav::PropName<All>) -> PropertyStream<'static> {
let calname = self.calname.to_string();
- futures::stream::iter(prop.0).map(move |n| {
- let prop = match n {
- dav::PropertyRequest::DisplayName => dav::Property::DisplayName(format!("{} calendar", calname)),
- dav::PropertyRequest::ResourceType => dav::Property::ResourceType(vec![
- dav::ResourceType::Collection,
- dav::ResourceType::Extension(all::ResourceType::Cal(cal::ResourceType::Calendar)),
- ]),
- //dav::PropertyRequest::GetContentType => dav::AnyProperty::Value(dav::Property::GetContentType("httpd/unix-directory".into())),
- //@FIXME seems wrong but seems to be what Thunderbird expects...
- dav::PropertyRequest::GetContentType => dav::Property::GetContentType("text/calendar".into()),
- dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::SupportedCalendarComponentSet))
- => dav::Property::Extension(all::Property::Cal(cal::Property::SupportedCalendarComponentSet(vec![
- cal::CompSupport(cal::Component::VEvent),
- ]))),
- v => return Err(v),
- };
- Ok(prop)
- }).boxed()
+ futures::stream::iter(prop.0)
+ .map(move |n| {
+ let prop = match n {
+ dav::PropertyRequest::DisplayName => {
+ dav::Property::DisplayName(format!("{} calendar", calname))
+ }
+ dav::PropertyRequest::ResourceType => dav::Property::ResourceType(vec![
+ dav::ResourceType::Collection,
+ dav::ResourceType::Extension(all::ResourceType::Cal(
+ cal::ResourceType::Calendar,
+ )),
+ ]),
+ //dav::PropertyRequest::GetContentType => dav::AnyProperty::Value(dav::Property::GetContentType("httpd/unix-directory".into())),
+ //@FIXME seems wrong but seems to be what Thunderbird expects...
+ dav::PropertyRequest::GetContentType => {
+ dav::Property::GetContentType("text/calendar".into())
+ }
+ dav::PropertyRequest::Extension(all::PropertyRequest::Cal(
+ cal::PropertyRequest::SupportedCalendarComponentSet,
+ )) => dav::Property::Extension(all::Property::Cal(
+ cal::Property::SupportedCalendarComponentSet(vec![cal::CompSupport(
+ cal::Component::VEvent,
+ )]),
+ )),
+ v => return Err(v),
+ };
+ Ok(prop)
+ })
+ .boxed()
}
- fn put<'a>(&'a self, _policy: PutPolicy, _stream: Content<'a>) -> BoxFuture<'a, std::result::Result<Etag, std::io::Error>> {
+ fn put<'a>(
+ &'a self,
+ _policy: PutPolicy,
+ _stream: Content<'a>,
+ ) -> BoxFuture<'a, std::result::Result<Etag, std::io::Error>> {
futures::future::err(std::io::Error::from(std::io::ErrorKind::Unsupported)).boxed()
}
- fn content<'a>(&'a self) -> Content<'static> {
- futures::stream::once(futures::future::err(std::io::Error::from(std::io::ErrorKind::Unsupported))).boxed()
+ fn content<'a>(&self) -> Content<'a> {
+ futures::stream::once(futures::future::err(std::io::Error::from(
+ std::io::ErrorKind::Unsupported,
+ )))
+ .boxed()
}
fn content_type(&self) -> &str {
@@ -386,13 +504,23 @@ pub(crate) struct EventNode {
}
impl DavNode for EventNode {
- fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str], create: bool) -> BoxFuture<'a, Result<Box<dyn DavNode>>> {
+ fn fetch<'a>(
+ &self,
+ user: &'a ArcUser,
+ path: &'a [&str],
+ create: bool,
+ ) -> BoxFuture<'a, Result<Box<dyn DavNode>>> {
if path.len() == 0 {
let node = Box::new(self.clone()) as Box<dyn DavNode>;
- return async { Ok(node) }.boxed()
+ return async { Ok(node) }.boxed();
}
- async { Err(anyhow!("Not supported: can't create a child on an event node")) }.boxed()
+ async {
+ Err(anyhow!(
+ "Not supported: can't create a child on an event node"
+ ))
+ }
+ .boxed()
}
fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec<Box<dyn DavNode>>> {
@@ -400,7 +528,10 @@ impl DavNode for EventNode {
}
fn path(&self, user: &ArcUser) -> String {
- format!("/{}/calendar/{}/{}", user.username, self.calname, self.filename)
+ format!(
+ "/{}/calendar/{}/{}",
+ user.username, self.calname, self.filename
+ )
}
fn supported_properties(&self, user: &ArcUser) -> dav::PropName<All> {
@@ -408,66 +539,106 @@ impl DavNode for EventNode {
dav::PropertyRequest::DisplayName,
dav::PropertyRequest::ResourceType,
dav::PropertyRequest::GetEtag,
- dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::CalendarData(cal::CalendarDataRequest::default()))),
+ dav::PropertyRequest::Extension(all::PropertyRequest::Cal(
+ cal::PropertyRequest::CalendarData(cal::CalendarDataRequest::default()),
+ )),
])
}
fn properties(&self, _user: &ArcUser, prop: dav::PropName<All>) -> PropertyStream<'static> {
let this = self.clone();
- futures::stream::iter(prop.0).then(move |n| {
- let this = this.clone();
-
- async move {
- let prop = match &n {
- dav::PropertyRequest::DisplayName => dav::Property::DisplayName(format!("{} event", this.filename)),
- dav::PropertyRequest::ResourceType => dav::Property::ResourceType(vec![]),
- dav::PropertyRequest::GetContentType => dav::Property::GetContentType("text/calendar".into()),
- dav::PropertyRequest::GetEtag => {
- let etag = this.etag().await.ok_or(n.clone())?;
- dav::Property::GetEtag(etag)
- },
- dav::PropertyRequest::Extension(all::PropertyRequest::Cal(cal::PropertyRequest::CalendarData(_req))) => {
- let ics = String::from_utf8(this.col.get(this.blob_id).await.or(Err(n.clone()))?).or(Err(n.clone()))?;
-
- dav::Property::Extension(all::Property::Cal(cal::Property::CalendarData(cal::CalendarDataPayload {
- mime: None,
- payload: ics,
- })))
- },
- _ => return Err(n),
- };
- Ok(prop)
- }
- }).boxed()
- }
-
- fn put<'a>(&'a self, policy: PutPolicy, stream: Content<'a>) -> BoxFuture<'a, std::result::Result<Etag, std::io::Error>> {
+ futures::stream::iter(prop.0)
+ .then(move |n| {
+ let this = this.clone();
+
+ async move {
+ let prop = match &n {
+ dav::PropertyRequest::DisplayName => {
+ dav::Property::DisplayName(format!("{} event", this.filename))
+ }
+ dav::PropertyRequest::ResourceType => dav::Property::ResourceType(vec![]),
+ dav::PropertyRequest::GetContentType => {
+ dav::Property::GetContentType("text/calendar".into())
+ }
+ dav::PropertyRequest::GetEtag => {
+ let etag = this.etag().await.ok_or(n.clone())?;
+ dav::Property::GetEtag(etag)
+ }
+ dav::PropertyRequest::Extension(all::PropertyRequest::Cal(
+ cal::PropertyRequest::CalendarData(_req),
+ )) => {
+ let ics = String::from_utf8(
+ this.col.get(this.blob_id).await.or(Err(n.clone()))?,
+ )
+ .or(Err(n.clone()))?;
+
+ dav::Property::Extension(all::Property::Cal(
+ cal::Property::CalendarData(cal::CalendarDataPayload {
+ mime: None,
+ payload: ics,
+ }),
+ ))
+ }
+ _ => return Err(n),
+ };
+ Ok(prop)
+ }
+ })
+ .boxed()
+ }
+
+ fn put<'a>(
+ &'a self,
+ policy: PutPolicy,
+ stream: Content<'a>,
+ ) -> BoxFuture<'a, std::result::Result<Etag, std::io::Error>> {
async {
- let existing_etag = self.etag().await.ok_or(std::io::Error::new(std::io::ErrorKind::Other, "Etag error"))?;
+ let existing_etag = self
+ .etag()
+ .await
+ .ok_or(std::io::Error::new(std::io::ErrorKind::Other, "Etag error"))?;
match policy {
- PutPolicy::CreateOnly => return Err(std::io::Error::from(std::io::ErrorKind::AlreadyExists)),
- PutPolicy::ReplaceEtag(etag) if etag != existing_etag.as_str() => return Err(std::io::Error::from(std::io::ErrorKind::AlreadyExists)),
- _ => ()
+ PutPolicy::CreateOnly => {
+ return Err(std::io::Error::from(std::io::ErrorKind::AlreadyExists))
+ }
+ PutPolicy::ReplaceEtag(etag) if etag != existing_etag.as_str() => {
+ return Err(std::io::Error::from(std::io::ErrorKind::AlreadyExists))
+ }
+ _ => (),
};
//@FIXME for now, our storage interface does not allow streaming,
// so we load everything in memory
let mut evt = Vec::new();
let mut reader = stream.into_async_read();
- reader.read_to_end(&mut evt).await.or(Err(std::io::Error::from(std::io::ErrorKind::BrokenPipe)))?;
- let (_token, entry) = self.col.put(self.filename.as_str(), evt.as_ref()).await.or(Err(std::io::ErrorKind::Interrupted))?;
- self.col.opportunistic_sync().await.or(Err(std::io::ErrorKind::ConnectionReset))?;
+ reader
+ .read_to_end(&mut evt)
+ .await
+ .or(Err(std::io::Error::from(std::io::ErrorKind::BrokenPipe)))?;
+ let (_token, entry) = self
+ .col
+ .put(self.filename.as_str(), evt.as_ref())
+ .await
+ .or(Err(std::io::ErrorKind::Interrupted))?;
+ self.col
+ .opportunistic_sync()
+ .await
+ .or(Err(std::io::ErrorKind::ConnectionReset))?;
Ok(entry.2)
- }.boxed()
+ }
+ .boxed()
}
- fn content<'a>(&'a self) -> Content<'static> {
+ fn content<'a>(&self) -> Content<'a> {
//@FIXME for now, our storage interface does not allow streaming,
// so we load everything in memory
let calendar = self.col.clone();
let blob_id = self.blob_id.clone();
let r = async move {
- let content = calendar.get(blob_id).await.or(Err(std::io::Error::from(std::io::ErrorKind::Interrupted)));
+ let content = calendar
+ .get(blob_id)
+ .await
+ .or(Err(std::io::Error::from(std::io::ErrorKind::Interrupted)));
Ok(hyper::body::Bytes::from(content?))
};
futures::stream::once(Box::pin(r)).boxed()
@@ -481,8 +652,14 @@ impl DavNode for EventNode {
let calendar = self.col.clone();
async move {
- calendar.dag().await.table.get(&self.blob_id).map(|(_, _, etag)| etag.to_string())
- }.boxed()
+ calendar
+ .dag()
+ .await
+ .table
+ .get(&self.blob_id)
+ .map(|(_, _, etag)| etag.to_string())
+ }
+ .boxed()
}
fn delete(&self) -> BoxFuture<std::result::Result<(), std::io::Error>> {
@@ -494,12 +671,16 @@ impl DavNode for EventNode {
Ok(v) => v,
Err(e) => {
tracing::error!(err=?e, "delete event node");
- return Err(std::io::Error::from(std::io::ErrorKind::Interrupted))
- },
+ return Err(std::io::Error::from(std::io::ErrorKind::Interrupted));
+ }
};
- calendar.opportunistic_sync().await.or(Err(std::io::ErrorKind::ConnectionReset))?;
+ calendar
+ .opportunistic_sync()
+ .await
+ .or(Err(std::io::ErrorKind::ConnectionReset))?;
Ok(())
- }.boxed()
+ }
+ .boxed()
}
}
@@ -510,13 +691,23 @@ pub(crate) struct CreateEventNode {
filename: String,
}
impl DavNode for CreateEventNode {
- fn fetch<'a>(&self, user: &'a ArcUser, path: &'a [&str], create: bool) -> BoxFuture<'a, Result<Box<dyn DavNode>>> {
+ fn fetch<'a>(
+ &self,
+ user: &'a ArcUser,
+ path: &'a [&str],
+ create: bool,
+ ) -> BoxFuture<'a, Result<Box<dyn DavNode>>> {
if path.len() == 0 {
let node = Box::new(self.clone()) as Box<dyn DavNode>;
- return async { Ok(node) }.boxed()
+ return async { Ok(node) }.boxed();
}
- async { Err(anyhow!("Not supported: can't create a child on an event node")) }.boxed()
+ async {
+ Err(anyhow!(
+ "Not supported: can't create a child on an event node"
+ ))
+ }
+ .boxed()
}
fn children<'a>(&self, user: &'a ArcUser) -> BoxFuture<'a, Vec<Box<dyn DavNode>>> {
@@ -524,33 +715,51 @@ impl DavNode for CreateEventNode {
}
fn path(&self, user: &ArcUser) -> String {
- format!("/{}/calendar/{}/{}", user.username, self.calname, self.filename)
+ format!(
+ "/{}/calendar/{}/{}",
+ user.username, self.calname, self.filename
+ )
}
fn supported_properties(&self, user: &ArcUser) -> dav::PropName<All> {
dav::PropName(vec![])
}
- fn properties(&self, _user: &ArcUser, prop: dav::PropName<All>) -> PropertyStream<'static> {
+ fn properties(&self, _user: &ArcUser, prop: dav::PropName<All>) -> PropertyStream<'static> {
futures::stream::iter(vec![]).boxed()
}
- fn put<'a>(&'a self, _policy: PutPolicy, stream: Content<'a>) -> BoxFuture<'a, std::result::Result<Etag, std::io::Error>> {
+ fn put<'a>(
+ &'a self,
+ _policy: PutPolicy,
+ stream: Content<'a>,
+ ) -> BoxFuture<'a, std::result::Result<Etag, std::io::Error>> {
//@NOTE: policy might not be needed here: whatever we put, there is no known entries here
-
+
async {
//@FIXME for now, our storage interface does not allow for streaming
let mut evt = Vec::new();
let mut reader = stream.into_async_read();
reader.read_to_end(&mut evt).await.unwrap();
- let (_token, entry) = self.col.put(self.filename.as_str(), evt.as_ref()).await.or(Err(std::io::ErrorKind::Interrupted))?;
- self.col.opportunistic_sync().await.or(Err(std::io::ErrorKind::ConnectionReset))?;
+ let (_token, entry) = self
+ .col
+ .put(self.filename.as_str(), evt.as_ref())
+ .await
+ .or(Err(std::io::ErrorKind::Interrupted))?;
+ self.col
+ .opportunistic_sync()
+ .await
+ .or(Err(std::io::ErrorKind::ConnectionReset))?;
Ok(entry.2)
- }.boxed()
+ }
+ .boxed()
}
- fn content(&self) -> Content<'static> {
- futures::stream::once(futures::future::err(std::io::Error::from(std::io::ErrorKind::Unsupported))).boxed()
+ fn content<'a>(&self) -> Content<'a> {
+ futures::stream::once(futures::future::err(std::io::Error::from(
+ std::io::ErrorKind::Unsupported,
+ )))
+ .boxed()
}
fn content_type(&self) -> &str {
diff --git a/aero-proto/src/imap/command/anonymous.rs b/aero-proto/src/imap/command/anonymous.rs
index 2848c30..f23ec17 100644
--- a/aero-proto/src/imap/command/anonymous.rs
+++ b/aero-proto/src/imap/command/anonymous.rs
@@ -4,8 +4,8 @@ use imap_codec::imap_types::core::AString;
use imap_codec::imap_types::response::Code;
use imap_codec::imap_types::secret::Secret;
-use aero_user::login::ArcLoginProvider;
use aero_collections::user::User;
+use aero_user::login::ArcLoginProvider;
use crate::imap::capability::ServerCapability;
use crate::imap::command::anystate;
diff --git a/aero-proto/src/imap/command/authenticated.rs b/aero-proto/src/imap/command/authenticated.rs
index 4c8d8c1..5bd34cb 100644
--- a/aero-proto/src/imap/command/authenticated.rs
+++ b/aero-proto/src/imap/command/authenticated.rs
@@ -14,10 +14,10 @@ use imap_codec::imap_types::mailbox::{ListMailbox, Mailbox as MailboxCodec};
use imap_codec::imap_types::response::{Code, CodeOther, Data};
use imap_codec::imap_types::status::{StatusDataItem, StatusDataItemName};
+use aero_collections::mail::namespace::MAILBOX_HIERARCHY_DELIMITER as MBX_HIER_DELIM_RAW;
use aero_collections::mail::uidindex::*;
-use aero_collections::user::User;
use aero_collections::mail::IMF;
-use aero_collections::mail::namespace::MAILBOX_HIERARCHY_DELIMITER as MBX_HIER_DELIM_RAW;
+use aero_collections::user::User;
use crate::imap::capability::{ClientCapability, ServerCapability};
use crate::imap::command::{anystate, MailboxName};
diff --git a/aero-proto/src/imap/mod.rs b/aero-proto/src/imap/mod.rs
index 7183a78..6a768b0 100644
--- a/aero-proto/src/imap/mod.rs
+++ b/aero-proto/src/imap/mod.rs
@@ -17,14 +17,14 @@ use std::net::SocketAddr;
use anyhow::{anyhow, bail, Result};
use futures::stream::{FuturesUnordered, StreamExt};
-use tokio::net::TcpListener;
-use tokio::sync::mpsc;
-use tokio::sync::watch;
use imap_codec::imap_types::response::{Code, CommandContinuationRequest, Response, Status};
use imap_codec::imap_types::{core::Text, response::Greeting};
use imap_flow::server::{ServerFlow, ServerFlowEvent, ServerFlowOptions};
use imap_flow::stream::AnyStream;
use rustls_pemfile::{certs, private_key};
+use tokio::net::TcpListener;
+use tokio::sync::mpsc;
+use tokio::sync::watch;
use tokio_rustls::TlsAcceptor;
use aero_user::config::{ImapConfig, ImapUnsecureConfig};
diff --git a/aero-proto/src/lmtp.rs b/aero-proto/src/lmtp.rs
index 9d40296..a82a783 100644
--- a/aero-proto/src/lmtp.rs
+++ b/aero-proto/src/lmtp.rs
@@ -10,16 +10,16 @@ use futures::{
stream::{FuturesOrdered, FuturesUnordered},
StreamExt,
};
+use smtp_message::{DataUnescaper, Email, EscapedDataReader, Reply, ReplyCode};
+use smtp_server::{reply, Config, ConnectionMetadata, Decision, MailMetadata};
use tokio::net::TcpListener;
use tokio::select;
use tokio::sync::watch;
use tokio_util::compat::*;
-use smtp_message::{DataUnescaper, Email, EscapedDataReader, Reply, ReplyCode};
-use smtp_server::{reply, Config, ConnectionMetadata, Decision, MailMetadata};
+use aero_collections::mail::incoming::EncryptedMessage;
use aero_user::config::*;
use aero_user::login::*;
-use aero_collections::mail::incoming::EncryptedMessage;
pub struct LmtpServer {
bind_addr: SocketAddr,
diff --git a/aero-proto/src/sasl.rs b/aero-proto/src/sasl.rs
index dae89eb..48c0815 100644
--- a/aero-proto/src/sasl.rs
+++ b/aero-proto/src/sasl.rs
@@ -8,9 +8,9 @@ use tokio::net::{TcpListener, TcpStream};
use tokio::sync::watch;
use tokio_util::bytes::BytesMut;
+use aero_sasl::{decode::client_command, encode::Encode, flow::State};
use aero_user::config::AuthConfig;
use aero_user::login::ArcLoginProvider;
-use aero_sasl::{flow::State, decode::client_command, encode::Encode};
pub struct AuthServer {
login_provider: ArcLoginProvider,
diff --git a/aero-sasl/src/flow.rs b/aero-sasl/src/flow.rs
index 31c8bc5..5aa4869 100644
--- a/aero-sasl/src/flow.rs
+++ b/aero-sasl/src/flow.rs
@@ -1,8 +1,8 @@
use futures::Future;
use rand::prelude::*;
-use super::types::*;
use super::decode::auth_plain;
+use super::types::*;
#[derive(Debug)]
pub enum AuthRes {
@@ -29,10 +29,10 @@ impl State {
}
async fn try_auth_plain<X, F>(&self, data: &[u8], login: X) -> AuthRes
- where
- X: FnOnce(String, String) -> F,
- F: Future<Output=bool>,
- {
+ where
+ X: FnOnce(String, String) -> F,
+ F: Future<Output = bool>,
+ {
// Check that we can extract user's login+pass
let (ubin, pbin) = match auth_plain(&data) {
Ok(([], (authz, user, pass))) if authz == user || authz == EMPTY_AUTHZ => (user, pass),
@@ -65,10 +65,10 @@ impl State {
}
}
- pub async fn progress<F,X>(&mut self, cmd: ClientCommand, login: X)
- where
- X: FnOnce(String, String) -> F,
- F: Future<Output=bool>,
+ pub async fn progress<F, X>(&mut self, cmd: ClientCommand, login: X)
+ where
+ X: FnOnce(String, String) -> F,
+ F: Future<Output = bool>,
{
let new_state = 'state: {
match (std::mem::replace(self, State::Error), cmd) {
diff --git a/aero-sasl/src/lib.rs b/aero-sasl/src/lib.rs
index 230862a..fdaa8a7 100644
--- a/aero-sasl/src/lib.rs
+++ b/aero-sasl/src/lib.rs
@@ -1,3 +1,6 @@
+pub mod decode;
+pub mod encode;
+pub mod flow;
/// Seek compatibility with the Dovecot Authentication Protocol
///
/// ## Trace
@@ -38,6 +41,3 @@
/// https://doc.dovecot.org/configuration_manual/howto/simple_virtual_install/#simple-virtual-install-smtp-auth
/// https://doc.dovecot.org/configuration_manual/howto/postfix_and_dovecot_sasl/#howto-postfix-and-dovecot-sasl
pub mod types;
-pub mod encode;
-pub mod decode;
-pub mod flow;
diff --git a/aero-sasl/src/types.rs b/aero-sasl/src/types.rs
index d71405e..2686677 100644
--- a/aero-sasl/src/types.rs
+++ b/aero-sasl/src/types.rs
@@ -159,5 +159,3 @@ pub enum ServerCommand {
extra_parameters: Vec<Vec<u8>>,
},
}
-
-
diff --git a/aero-user/src/config.rs b/aero-user/src/config.rs
index 44b1239..cea4520 100644
--- a/aero-user/src/config.rs
+++ b/aero-user/src/config.rs
@@ -11,7 +11,6 @@ pub struct CompanionConfig {
pub pid: Option<PathBuf>,
pub imap: ImapUnsecureConfig,
// @FIXME Add DAV
-
#[serde(flatten)]
pub users: LoginStaticConfig,
}
diff --git a/aero-user/src/login/ldap_provider.rs b/aero-user/src/login/ldap_provider.rs
index ca5a356..22b301e 100644
--- a/aero-user/src/login/ldap_provider.rs
+++ b/aero-user/src/login/ldap_provider.rs
@@ -2,9 +2,9 @@ use async_trait::async_trait;
use ldap3::{LdapConnAsync, Scope, SearchEntry};
use log::debug;
+use super::*;
use crate::config::*;
use crate::storage;
-use super::*;
pub struct LdapLoginProvider {
ldap_server: String,
diff --git a/aero-user/src/storage/in_memory.rs b/aero-user/src/storage/in_memory.rs
index 9ef2721..5c8eb26 100644
--- a/aero-user/src/storage/in_memory.rs
+++ b/aero-user/src/storage/in_memory.rs
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use std::ops::Bound::{self, Excluded, Included, Unbounded};
use std::sync::RwLock;
-use sodiumoxide::{hex, crypto::hash};
+use sodiumoxide::{crypto::hash, hex};
use tokio::sync::Notify;
use crate::storage::*;
diff --git a/aerogramme/src/main.rs b/aerogramme/src/main.rs
index 624e8e2..39b5075 100644
--- a/aerogramme/src/main.rs
+++ b/aerogramme/src/main.rs
@@ -7,9 +7,9 @@ use anyhow::{bail, Context, Result};
use clap::{Parser, Subcommand};
use nix::{sys::signal, unistd::Pid};
+use crate::server::Server;
use aero_user::config::*;
use aero_user::login::{static_provider::*, *};
-use crate::server::Server;
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
diff --git a/aerogramme/src/server.rs b/aerogramme/src/server.rs
index e57cd72..3b3f6eb 100644
--- a/aerogramme/src/server.rs
+++ b/aerogramme/src/server.rs
@@ -7,13 +7,13 @@ use futures::try_join;
use log::*;
use tokio::sync::watch;
-use aero_user::config::*;
-use aero_user::login::ArcLoginProvider;
-use aero_user::login::{demo_provider::*, ldap_provider::*, static_provider::*};
-use aero_proto::sasl as auth;
use aero_proto::dav;
use aero_proto::imap;
use aero_proto::lmtp::*;
+use aero_proto::sasl as auth;
+use aero_user::config::*;
+use aero_user::login::ArcLoginProvider;
+use aero_user::login::{demo_provider::*, ldap_provider::*, static_provider::*};
pub struct Server {
lmtp_server: Option<Arc<LmtpServer>>,