WebDAV permissions and logging (closes #1362)
This commit is contained in:
@@ -4,6 +4,7 @@ version = "0.11.7"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
trc = { path = "../trc" }
|
||||
hashify = "0.2.6"
|
||||
quick-xml = "0.37.2"
|
||||
calcard = { path = "/Users/me/code/calcard", features = ["rkyv"] }
|
||||
@@ -11,6 +12,7 @@ mail-parser = "0.10.2"
|
||||
hyper = "1.6.0"
|
||||
rkyv = { version = "0.8.10", features = ["little_endian"] }
|
||||
chrono = { version = "0.4.40", features = ["serde"], optional = true }
|
||||
compact_str = "0.9.0"
|
||||
|
||||
[dev-dependencies]
|
||||
calcard = { path = "/Users/me/code/calcard", features = ["serde", "rkyv"] }
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use compact_str::{CompactString, ToCompactString};
|
||||
use trc::Value;
|
||||
|
||||
pub mod parser;
|
||||
pub mod requests;
|
||||
pub mod responses;
|
||||
@@ -50,7 +53,7 @@ pub struct ResourceState<T: AsRef<str>> {
|
||||
pub state_token: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum Return {
|
||||
Minimal,
|
||||
Representation,
|
||||
@@ -91,16 +94,87 @@ pub enum Depth {
|
||||
None,
|
||||
}
|
||||
|
||||
impl From<&RequestHeaders<'_>> for Value {
|
||||
fn from(headers: &RequestHeaders<'_>) -> Self {
|
||||
let mut values = Vec::with_capacity(4);
|
||||
if headers.depth != Depth::None {
|
||||
values.push(Value::String(CompactString::const_new("Depth")));
|
||||
values.push(match headers.depth {
|
||||
Depth::Zero => Value::Int(0),
|
||||
Depth::One => Value::Int(1),
|
||||
Depth::Infinity => Value::String(CompactString::const_new("infinity")),
|
||||
Depth::None => Value::None,
|
||||
});
|
||||
}
|
||||
if headers.timeout != Timeout::None {
|
||||
values.push(Value::String(CompactString::const_new("Timeout")));
|
||||
values.push(match headers.timeout {
|
||||
Timeout::Infinite => Value::String(CompactString::const_new("infinite")),
|
||||
Timeout::Second(n) => Value::Int(n as i64),
|
||||
Timeout::None => Value::None,
|
||||
});
|
||||
}
|
||||
for (name, header_value) in [
|
||||
("Content-Type", headers.content_type),
|
||||
("Destination", headers.destination),
|
||||
("Lock-Token", headers.lock_token),
|
||||
] {
|
||||
if let Some(value) = header_value {
|
||||
values.push(CompactString::const_new(name).into());
|
||||
values.push(value.to_compact_string().into());
|
||||
}
|
||||
}
|
||||
for (name, is_set) in [
|
||||
("Overwrite", headers.overwrite_fail),
|
||||
("No-Timezones", headers.no_timezones),
|
||||
("Depth-No-Root", headers.depth_no_root),
|
||||
] {
|
||||
if is_set {
|
||||
values.push(CompactString::const_new(name).into());
|
||||
}
|
||||
}
|
||||
for if_ in &headers.if_ {
|
||||
values.push(CompactString::const_new("If").into());
|
||||
let mut if_values = Vec::with_capacity(if_.list.len() * 2 + 1);
|
||||
if let Some(resource) = if_.resource {
|
||||
if_values.push(Value::String(resource.to_compact_string()));
|
||||
}
|
||||
for condition in &if_.list {
|
||||
match condition {
|
||||
Condition::StateToken { is_not, token } => {
|
||||
if *is_not {
|
||||
if_values.push(Value::String(CompactString::const_new("!State-Token")));
|
||||
} else {
|
||||
if_values.push(Value::String(CompactString::const_new("State-Token")));
|
||||
}
|
||||
if_values.push(Value::String(token.to_compact_string()));
|
||||
}
|
||||
Condition::ETag { is_not, tag } => {
|
||||
if *is_not {
|
||||
if_values.push(Value::String(CompactString::const_new("!ETag")));
|
||||
} else {
|
||||
if_values.push(Value::String(CompactString::const_new("ETag")));
|
||||
}
|
||||
if_values.push(Value::String(tag.to_compact_string()));
|
||||
}
|
||||
Condition::Exists { is_not } => {
|
||||
if *is_not {
|
||||
if_values.push(Value::String(CompactString::const_new("!Exists")));
|
||||
} else {
|
||||
if_values.push(Value::String(CompactString::const_new("Exists")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
values.push(Value::Array(if_values));
|
||||
}
|
||||
|
||||
Value::Array(values)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Allow: OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, COPY, MOVE
|
||||
Allow: MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, REPORT, ACL
|
||||
DAV: 1, 2, 3, access-control, extended-mkcol
|
||||
calendar-no-timezone
|
||||
|
||||
|
||||
TODO:
|
||||
|
||||
|
||||
Implemented:
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::{Display, Formatter},
|
||||
};
|
||||
|
||||
use quick_xml::events::BytesStart;
|
||||
use tokenizer::Tokenizer;
|
||||
@@ -152,3 +155,18 @@ impl Default for RawElement<'_> {
|
||||
RawElement(BytesStart::new(""))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Error::Xml(err) => write!(f, "XML error: {}", err),
|
||||
Error::UnexpectedToken { expected, found } => {
|
||||
write!(f, "Unexpected token: {found:?}")?;
|
||||
if let Some(expected) = expected {
|
||||
write!(f, ", expected: {expected:?}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,6 +288,85 @@ impl MultiStatus {
|
||||
}
|
||||
}
|
||||
|
||||
impl BaseCondition {
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
BaseCondition::NoConflictingLock(_) => "NoConflictingLock",
|
||||
BaseCondition::CannotModifyProtectedProperty => "CannotModifyProtectedProperty",
|
||||
BaseCondition::LockTokenSubmitted(_) => "LockTokenSubmitted",
|
||||
BaseCondition::LockTokenMatchesRequestUri => "LockTokenMatchesRequestUri",
|
||||
BaseCondition::NoExternalEntities => "NoExternalEntities",
|
||||
BaseCondition::PreservedLiveProperties => "PreservedLiveProperties",
|
||||
BaseCondition::PropFindFiniteDepth => "PropFindFiniteDepth",
|
||||
BaseCondition::ResourceMustBeNull => "ResourceMustBeNull",
|
||||
BaseCondition::NeedPrivileges(_) => "NeedPrivileges",
|
||||
BaseCondition::NoAceConflict => "NoAceConflict",
|
||||
BaseCondition::NoProtectedAceConflict => "NoProtectedAceConflict",
|
||||
BaseCondition::NoInheritedAceConflict => "NoInheritedAceConflict",
|
||||
BaseCondition::LimitedNumberOfAces => "LimitedNumberOfAces",
|
||||
BaseCondition::DenyBeforeGrant => "DenyBeforeGrant",
|
||||
BaseCondition::GrantOnly => "GrantOnly",
|
||||
BaseCondition::NoInvert => "NoInvert",
|
||||
BaseCondition::NoAbstract => "NoAbstract",
|
||||
BaseCondition::NotSupportedPrivilege => "NotSupportedPrivilege",
|
||||
BaseCondition::MissingRequiredPrincipal => "MissingRequiredPrincipal",
|
||||
BaseCondition::RecognizedPrincipal => "RecognizedPrincipal",
|
||||
BaseCondition::AllowedPrincipal => "AllowedPrincipal",
|
||||
BaseCondition::NumberOfMatchesWithinLimit => "NumberOfMatchesWithinLimit",
|
||||
BaseCondition::QuotaNotExceeded => "QuotaNotExceeded",
|
||||
BaseCondition::ValidResourceType => "ValidResourceType",
|
||||
BaseCondition::ValidSyncToken => "ValidSyncToken",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CalCondition {
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
CalCondition::CalendarCollectionLocationOk => "CalendarCollectionLocationOk",
|
||||
CalCondition::ValidCalendarData => "ValidCalendarData",
|
||||
CalCondition::ValidFilter => "ValidFilter",
|
||||
CalCondition::ValidCalendarObjectResource => "ValidCalendarObjectResource",
|
||||
CalCondition::ValidTimezone => "ValidTimezone",
|
||||
CalCondition::NoUidConflict(_) => "NoUidConflict",
|
||||
CalCondition::InitializeCalendarCollection => "InitializeCalendarCollection",
|
||||
CalCondition::SupportedCalendarData => "SupportedCalendarData",
|
||||
CalCondition::SupportedFilter(_) => "SupportedFilter",
|
||||
CalCondition::SupportedCollation(_) => "SupportedCollation",
|
||||
CalCondition::MinDateTime => "MinDateTime",
|
||||
CalCondition::MaxDateTime => "MaxDateTime",
|
||||
CalCondition::MaxResourceSize(_) => "MaxResourceSize",
|
||||
CalCondition::MaxInstances => "MaxInstances",
|
||||
CalCondition::MaxAttendeesPerInstance => "MaxAttendeesPerInstance",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CardCondition {
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
CardCondition::SupportedAddressData => "SupportedAddressData",
|
||||
CardCondition::SupportedAddressDataConversion => "SupportedAddressDataConversion",
|
||||
CardCondition::SupportedFilter(_) => "SupportedFilter",
|
||||
CardCondition::SupportedCollation(_) => "SupportedCollation",
|
||||
CardCondition::ValidAddressData => "ValidAddressData",
|
||||
CardCondition::NoUidConflict(_) => "NoUidConflict",
|
||||
CardCondition::MaxResourceSize(_) => "MaxResourceSize",
|
||||
CardCondition::AddressBookCollectionLocationOk => "AddressBookCollectionLocationOk",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Condition {
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
Condition::Base(base) => base.display_name(),
|
||||
Condition::Cal(cal) => cal.display_name(),
|
||||
Condition::Card(card) => card.display_name(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod serde_impl {
|
||||
use super::Status;
|
||||
|
||||
Reference in New Issue
Block a user