Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
793 lines
34 KiB
Rust
793 lines
34 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
*/
|
|
|
|
use crate::utils::server::TestServer;
|
|
use crate::utils::webdav::GenerateTestDavResource;
|
|
use crate::webdav::{TEST_ICAL_2, TEST_VTIMEZONE_1};
|
|
use dav_proto::schema::property::{
|
|
CalDavProperty, CardDavProperty, DavProperty, PrincipalProperty, WebDavProperty,
|
|
};
|
|
use groupware::DavResourceName;
|
|
use hyper::StatusCode;
|
|
use types::dead_property::DeadElementTag;
|
|
|
|
pub async fn test(test: &TestServer, assisted_discovery: bool) {
|
|
let client = test.account("[email protected]").webdav_client();
|
|
|
|
for resource_type in [
|
|
DavResourceName::File,
|
|
DavResourceName::Cal,
|
|
DavResourceName::Card,
|
|
] {
|
|
println!(
|
|
"Running PROPFIND/PROPPATCH tests ({})...",
|
|
resource_type.base_path()
|
|
);
|
|
let user_base_path = format!("{}/jane%40example.com", resource_type.base_path());
|
|
let group_base_path = format!("{}/support%40example.com", resource_type.base_path());
|
|
|
|
// Create a new test container and file
|
|
let test_base_path = format!("{user_base_path}/PropFind_Folder/");
|
|
let etag_folder = client
|
|
.mkcol("MKCOL", &test_base_path, [], [])
|
|
.await
|
|
.with_status(StatusCode::CREATED)
|
|
.etag()
|
|
.to_string();
|
|
let test_contents = resource_type.generate();
|
|
let test_path = format!("{test_base_path}test_file");
|
|
let etag_file = client
|
|
.request_with_headers(
|
|
"PUT",
|
|
&test_path,
|
|
[("content-type", "text/x-other")],
|
|
test_contents.as_str(),
|
|
)
|
|
.await
|
|
.with_status(StatusCode::CREATED)
|
|
.etag()
|
|
.to_string();
|
|
|
|
// Test 1: PROPFIND Depth 0 on root
|
|
client
|
|
.request_with_headers("PROPFIND", resource_type.base_path(), [("depth", "0")], "")
|
|
.await
|
|
.with_status(StatusCode::MULTI_STATUS)
|
|
.with_hrefs([resource_type.collection_path()]);
|
|
|
|
// Test 2: PROPFIND Depth 0 on user base path
|
|
client
|
|
.request_with_headers("PROPFIND", &user_base_path, [("depth", "0")], "")
|
|
.await
|
|
.with_status(StatusCode::MULTI_STATUS)
|
|
.with_hrefs([format!("{user_base_path}/").as_str()]);
|
|
|
|
client
|
|
.propfind_with_headers(
|
|
&user_base_path,
|
|
[DavProperty::WebDav(WebDavProperty::GetCTag)],
|
|
[("depth", "0")],
|
|
)
|
|
.await
|
|
.properties(&format!("{user_base_path}/"))
|
|
.get(DavProperty::WebDav(WebDavProperty::GetCTag))
|
|
.is_not_empty();
|
|
|
|
// Test 3: PROPFIND Depth 1 on root
|
|
client
|
|
.request_with_headers("PROPFIND", resource_type.base_path(), [("depth", "1")], "")
|
|
.await
|
|
.with_status(StatusCode::MULTI_STATUS)
|
|
.with_hrefs([
|
|
resource_type.collection_path(),
|
|
format!("{user_base_path}/").as_str(),
|
|
format!("{group_base_path}/").as_str(),
|
|
]);
|
|
|
|
// Test 4: Infinity depth is not allowed
|
|
for path in [resource_type.base_path(), user_base_path.as_str()] {
|
|
client
|
|
.request_with_headers("PROPFIND", path, [("depth", "infinity")], "")
|
|
.await
|
|
.with_status(StatusCode::FORBIDDEN);
|
|
}
|
|
|
|
// Test 5: PROPFIND Depth 1 on user base path
|
|
client
|
|
.request_with_headers("PROPFIND", &user_base_path, [("depth", "1")], "")
|
|
.await
|
|
.with_status(StatusCode::MULTI_STATUS)
|
|
.with_hrefs(
|
|
[
|
|
format!("{group_base_path}/default/").as_str(),
|
|
format!("{user_base_path}/default/").as_str(),
|
|
format!("{user_base_path}/").as_str(),
|
|
&test_base_path,
|
|
]
|
|
.into_iter()
|
|
.skip(if resource_type == DavResourceName::File {
|
|
2
|
|
} else if !assisted_discovery {
|
|
1
|
|
} else {
|
|
0
|
|
}),
|
|
);
|
|
|
|
// Test 6: PROPFIND Depth 1 on created collection
|
|
client
|
|
.request_with_headers("PROPFIND", &test_base_path, [("depth", "1")], "")
|
|
.await
|
|
.with_status(StatusCode::MULTI_STATUS)
|
|
.with_hrefs([test_base_path.as_str(), test_path.as_str()]);
|
|
|
|
// Test 7: Infinity depth is not allowed on file containers
|
|
client
|
|
.request_with_headers("PROPFIND", &test_base_path, [("depth", "infinity")], "")
|
|
.await
|
|
.with_status(if resource_type == DavResourceName::File {
|
|
StatusCode::FORBIDDEN
|
|
} else {
|
|
StatusCode::MULTI_STATUS
|
|
});
|
|
|
|
// Test 8 PROPFIND with depth-no-root
|
|
client
|
|
.request_with_headers(
|
|
"PROPFIND",
|
|
&user_base_path,
|
|
[("depth", "1"), ("prefer", "depth-noroot")],
|
|
"",
|
|
)
|
|
.await
|
|
.with_status(StatusCode::MULTI_STATUS)
|
|
.with_hrefs(
|
|
[
|
|
format!("{group_base_path}/default/").as_str(),
|
|
format!("{user_base_path}/default/").as_str(),
|
|
&test_base_path,
|
|
]
|
|
.into_iter()
|
|
.skip(if resource_type == DavResourceName::File {
|
|
2
|
|
} else if !assisted_discovery {
|
|
1
|
|
} else {
|
|
0
|
|
}),
|
|
);
|
|
client
|
|
.request_with_headers(
|
|
"PROPFIND",
|
|
&test_base_path,
|
|
[("depth", "1"), ("prefer", "depth-noroot")],
|
|
"",
|
|
)
|
|
.await
|
|
.with_status(StatusCode::MULTI_STATUS)
|
|
.with_hrefs([test_path.as_str()]);
|
|
|
|
// Test 8 PROPFIND with prefer return=minimal
|
|
let response = client
|
|
.propfind_with_headers(&test_base_path, ALL_DAV_PROPERTIES, [])
|
|
.await;
|
|
response
|
|
.properties(&test_base_path)
|
|
.is_defined(DavProperty::WebDav(WebDavProperty::GetETag))
|
|
.is_defined(DavProperty::Principal(PrincipalProperty::GroupMembership));
|
|
let response = client
|
|
.propfind_with_headers(
|
|
&test_base_path,
|
|
ALL_DAV_PROPERTIES,
|
|
[("prefer", "return=minimal")],
|
|
)
|
|
.await;
|
|
response
|
|
.properties(&test_base_path)
|
|
.is_defined(DavProperty::WebDav(WebDavProperty::GetETag))
|
|
.is_undefined(DavProperty::Principal(PrincipalProperty::GroupMembership));
|
|
|
|
// Test 9: Retrieve all static properties
|
|
for (path, etag, is_file) in [
|
|
(&test_base_path, &etag_folder, false),
|
|
(&test_path, &etag_file, true),
|
|
] {
|
|
let response = client.propfind(path, ALL_DAV_PROPERTIES).await;
|
|
let properties = response.properties(path);
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::CreationDate))
|
|
.is_not_empty();
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::GetLastModified))
|
|
.is_not_empty();
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::SyncToken))
|
|
.is_not_empty();
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::GetETag))
|
|
.with_values([etag.as_str()]);
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::SupportedLock))
|
|
.with_values([
|
|
"D:lockentry.D:lockscope.D:exclusive",
|
|
"D:lockentry.D:locktype.D:write",
|
|
"D:lockentry.D:lockscope.D:shared",
|
|
"D:lockentry.D:locktype.D:write",
|
|
]);
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal))
|
|
.with_values([format!(
|
|
"D:href:{}/jane%40example.com/",
|
|
DavResourceName::Principal.base_path()
|
|
)
|
|
.as_str()]);
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::Owner))
|
|
.with_values([format!(
|
|
"D:href:{}/jane%40example.com/",
|
|
DavResourceName::Principal.base_path()
|
|
)
|
|
.as_str()]);
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::SupportedPrivilegeSet))
|
|
.is_not_empty();
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::AclRestrictions))
|
|
.with_values(["D:grant-only", "D:no-invert"]);
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::PrincipalCollectionSet))
|
|
.with_values([
|
|
format!("D:href:{}", DavResourceName::Principal.collection_path()).as_str(),
|
|
]);
|
|
|
|
if is_file {
|
|
// File specific properties
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::GetContentType))
|
|
.with_values([match resource_type {
|
|
DavResourceName::File => "text/x-other",
|
|
DavResourceName::Cal => "text/calendar",
|
|
DavResourceName::Card => "text/vcard",
|
|
_ => unreachable!(),
|
|
}]);
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::GetContentLength))
|
|
.with_values([test_contents.len().to_string().as_str()]);
|
|
} else {
|
|
// Collection specific properties
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::GetCTag))
|
|
.is_not_empty();
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::ResourceType))
|
|
.with_values(match resource_type {
|
|
DavResourceName::File => ["D:collection"].as_slice().iter().copied(),
|
|
DavResourceName::Cal => {
|
|
["D:collection", "A:calendar"].as_slice().iter().copied()
|
|
}
|
|
DavResourceName::Card => {
|
|
["D:collection", "B:addressbook"].as_slice().iter().copied()
|
|
}
|
|
_ => unreachable!(),
|
|
});
|
|
let used_bytes: u64 = properties
|
|
.get(DavProperty::WebDav(WebDavProperty::QuotaUsedBytes))
|
|
.value()
|
|
.parse()
|
|
.unwrap();
|
|
assert!(used_bytes > 0);
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::QuotaAvailableBytes))
|
|
.with_status(StatusCode::NOT_FOUND);
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::SupportedReportSet))
|
|
.with_values(match resource_type {
|
|
DavResourceName::File => [
|
|
"D:supported-report.D:report.D:sync-collection",
|
|
"D:supported-report.D:report.D:acl-principal-prop-set",
|
|
"D:supported-report.D:report.D:principal-match",
|
|
]
|
|
.as_slice()
|
|
.iter()
|
|
.copied(),
|
|
DavResourceName::Cal => [
|
|
"D:supported-report.D:report.A:calendar-query",
|
|
"D:supported-report.D:report.D:sync-collection",
|
|
"D:supported-report.D:report.D:acl-principal-prop-set",
|
|
"D:supported-report.D:report.D:expand-property",
|
|
"D:supported-report.D:report.A:free-busy-query",
|
|
"D:supported-report.D:report.A:calendar-multiget",
|
|
"D:supported-report.D:report.D:principal-match",
|
|
]
|
|
.as_slice()
|
|
.iter()
|
|
.copied(),
|
|
DavResourceName::Card => [
|
|
"D:supported-report.D:report.B:addressbook-multiget",
|
|
"D:supported-report.D:report.D:sync-collection",
|
|
"D:supported-report.D:report.D:acl-principal-prop-set",
|
|
"D:supported-report.D:report.D:principal-match",
|
|
"D:supported-report.D:report.B:addressbook-query",
|
|
"D:supported-report.D:report.D:expand-property",
|
|
]
|
|
.as_slice()
|
|
.iter()
|
|
.copied(),
|
|
_ => unreachable!(),
|
|
});
|
|
|
|
if resource_type == DavResourceName::Cal {
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::CurrentUserPrivilegeSet))
|
|
.with_values([
|
|
"D:privilege.D:all",
|
|
"D:privilege.D:read",
|
|
"D:privilege.D:write",
|
|
"D:privilege.D:write-properties",
|
|
"D:privilege.D:write-content",
|
|
"D:privilege.D:unlock",
|
|
"D:privilege.D:read-acl",
|
|
"D:privilege.D:read-current-user-privilege-set",
|
|
"D:privilege.D:write-acl",
|
|
"D:privilege.D:bind",
|
|
"D:privilege.D:unbind",
|
|
"D:privilege.A:read-free-busy",
|
|
]);
|
|
properties
|
|
.get(DavProperty::CalDav(
|
|
CalDavProperty::SupportedCalendarComponentSet,
|
|
))
|
|
.with_values([
|
|
"A:comp.[name]:VAVAILABILITY",
|
|
"A:comp.[name]:AVAILABLE",
|
|
"A:comp.[name]:VRESOURCE",
|
|
"A:comp.[name]:VTODO",
|
|
"A:comp.[name]:DAYLIGHT",
|
|
"A:comp.[name]:STANDARD",
|
|
"A:comp.[name]:VLOCATION",
|
|
"A:comp.[name]:VTIMEZONE",
|
|
"A:comp.[name]:VFREEBUSY",
|
|
"A:comp.[name]:VEVENT",
|
|
"A:comp.[name]:VJOURNAL",
|
|
"A:comp.[name]:PARTICIPANT",
|
|
"A:comp.[name]:VALARM",
|
|
]);
|
|
properties
|
|
.get(DavProperty::CalDav(CalDavProperty::SupportedCalendarData))
|
|
.with_values([
|
|
concat!("A:calendar-data-type.", "[content-type]:text/calendar"),
|
|
"A:calendar-data-type.[version]:2.0",
|
|
"A:calendar-data-type.[version]:1.0",
|
|
]);
|
|
properties
|
|
.get(DavProperty::CalDav(CalDavProperty::SupportedCollationSet))
|
|
.with_values([
|
|
"A:supported-collation:i;unicode-casemap",
|
|
"A:supported-collation:i;ascii-casemap",
|
|
]);
|
|
properties
|
|
.get(DavProperty::CalDav(CalDavProperty::MinDateTime))
|
|
.with_values(["0001-01-01T00:00:00Z"]);
|
|
properties
|
|
.get(DavProperty::CalDav(CalDavProperty::MaxDateTime))
|
|
.with_values(["9999-12-31T23:59:59Z"]);
|
|
for (key, value) in [
|
|
(
|
|
DavProperty::CalDav(CalDavProperty::MaxResourceSize),
|
|
test.server.core.groupware.max_ical_size,
|
|
),
|
|
(
|
|
DavProperty::CalDav(CalDavProperty::MaxInstances),
|
|
test.server.core.groupware.max_ical_instances,
|
|
),
|
|
(
|
|
DavProperty::CalDav(CalDavProperty::MaxAttendeesPerInstance),
|
|
test.server.core.groupware.max_ical_attendees_per_instance,
|
|
),
|
|
] {
|
|
properties
|
|
.get(key)
|
|
.with_values([value.to_string().as_str()]);
|
|
}
|
|
} else {
|
|
if resource_type == DavResourceName::Card {
|
|
properties
|
|
.get(DavProperty::CardDav(CardDavProperty::SupportedAddressData))
|
|
.with_values([
|
|
concat!("B:address-data-type.", "[content-type]:text/vcard"),
|
|
"B:address-data-type.[version]:3.0",
|
|
"B:address-data-type.[version]:4.0",
|
|
"B:address-data-type.[version]:2.1",
|
|
]);
|
|
properties
|
|
.get(DavProperty::CardDav(CardDavProperty::SupportedCollationSet))
|
|
.with_values([
|
|
"B:supported-collation:i;unicode-casemap",
|
|
"B:supported-collation:i;ascii-casemap",
|
|
]);
|
|
properties
|
|
.get(DavProperty::CardDav(CardDavProperty::MaxResourceSize))
|
|
.with_values([test
|
|
.server
|
|
.core
|
|
.groupware
|
|
.max_vcard_size
|
|
.to_string()
|
|
.as_str()]);
|
|
}
|
|
|
|
properties
|
|
.get(DavProperty::WebDav(WebDavProperty::CurrentUserPrivilegeSet))
|
|
.with_values([
|
|
"D:privilege.D:all",
|
|
"D:privilege.D:read",
|
|
"D:privilege.D:write",
|
|
"D:privilege.D:write-properties",
|
|
"D:privilege.D:write-content",
|
|
"D:privilege.D:unlock",
|
|
"D:privilege.D:read-acl",
|
|
"D:privilege.D:read-current-user-privilege-set",
|
|
"D:privilege.D:write-acl",
|
|
"D:privilege.D:bind",
|
|
"D:privilege.D:unbind",
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test 10: expand-property report
|
|
for path in [&test_base_path, &test_path] {
|
|
let response = client
|
|
.request("REPORT", path, EXPAND_REPORT_QUERY)
|
|
.await
|
|
.with_status(StatusCode::MULTI_STATUS)
|
|
.into_propfind_response(None);
|
|
let properties = response.properties(path);
|
|
for prop in [
|
|
DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal),
|
|
DavProperty::WebDav(WebDavProperty::Owner),
|
|
] {
|
|
properties.get(prop).with_some_values([
|
|
format!(
|
|
"D:response.D:href:{}/jane%40example.com/",
|
|
DavResourceName::Principal.base_path(),
|
|
)
|
|
.as_str(),
|
|
"D:response.D:propstat.D:prop.D:displayname:Jane Doe-Smith",
|
|
]);
|
|
}
|
|
}
|
|
|
|
for (path, etag, is_file) in [
|
|
(&test_base_path, &etag_folder, false),
|
|
(&test_path, &etag_file, true),
|
|
] {
|
|
// Test 11: PROPPATCH should fail when a precondition fails
|
|
client
|
|
.proppatch(
|
|
path,
|
|
[(
|
|
DavProperty::WebDav(WebDavProperty::DisplayName),
|
|
"Magnific name",
|
|
)],
|
|
[],
|
|
[("if", format!("(Not [{etag}])").as_str())],
|
|
)
|
|
.await
|
|
.with_status(StatusCode::PRECONDITION_FAILED);
|
|
client
|
|
.proppatch(
|
|
path,
|
|
[(
|
|
DavProperty::WebDav(WebDavProperty::DisplayName),
|
|
"Magnific name - second try",
|
|
)],
|
|
[],
|
|
[("if", format!("([{etag}])").as_str())],
|
|
)
|
|
.await
|
|
.with_status(StatusCode::MULTI_STATUS);
|
|
client
|
|
.propfind(path, [DavProperty::WebDav(WebDavProperty::GetETag)])
|
|
.await
|
|
.properties(path)
|
|
.get(DavProperty::WebDav(WebDavProperty::GetETag))
|
|
.with_status(StatusCode::OK)
|
|
.without_values([etag.as_str()]);
|
|
|
|
// Test 12: PROPPATCH set on DAV properties
|
|
client
|
|
.patch_and_check(
|
|
path,
|
|
[
|
|
(
|
|
DavProperty::WebDav(WebDavProperty::DisplayName),
|
|
"New display name",
|
|
),
|
|
(
|
|
DavProperty::WebDav(WebDavProperty::CreationDate),
|
|
"2000-01-01T00:00:00Z",
|
|
),
|
|
(
|
|
DavProperty::DeadProperty(DeadElementTag::new(
|
|
"my-dead-element".to_string(),
|
|
Some("xmlns=\"http://example.com/ns/\" prop=\"abc\"".to_string()),
|
|
)),
|
|
"this is a dead but exciting element",
|
|
),
|
|
],
|
|
)
|
|
.await;
|
|
client
|
|
.patch_and_check(
|
|
path,
|
|
[(
|
|
DavProperty::DeadProperty(DeadElementTag::new(
|
|
"my-dead-element".to_string(),
|
|
Some("xmlns=\"http://example.com/ns/\" prop=\"xyz\"".to_string()),
|
|
)),
|
|
"this is a modified dead but exciting element",
|
|
)],
|
|
)
|
|
.await;
|
|
|
|
// Test 13: PROPPATCH remove on DAV properties
|
|
let mut props = vec![
|
|
(
|
|
DavProperty::DeadProperty(DeadElementTag::new(
|
|
"my-dead-element".to_string(),
|
|
Some("xmlns=\"http://example.com/ns/\"".to_string()),
|
|
)),
|
|
"",
|
|
),
|
|
(DavProperty::WebDav(WebDavProperty::DisplayName), ""),
|
|
];
|
|
if !is_file {
|
|
// DisplayName can't be removed from calendar/contact collections
|
|
props.pop();
|
|
}
|
|
client.patch_and_check(path, props).await;
|
|
|
|
match resource_type {
|
|
DavResourceName::File if is_file => {
|
|
// Test 14: Change a file's content-type
|
|
client
|
|
.patch_and_check(
|
|
path,
|
|
[(
|
|
DavProperty::WebDav(WebDavProperty::GetContentType),
|
|
"text/x-yadda-yadda",
|
|
)],
|
|
)
|
|
.await;
|
|
}
|
|
DavResourceName::Cal if !is_file => {
|
|
// Test 15: Change a calendar's properties
|
|
client
|
|
.patch_and_check(
|
|
path,
|
|
[
|
|
(
|
|
DavProperty::CalDav(CalDavProperty::CalendarDescription),
|
|
"New calendar description",
|
|
),
|
|
(
|
|
DavProperty::CalDav(CalDavProperty::TimezoneId),
|
|
"Europe/Ljubljana",
|
|
),
|
|
],
|
|
)
|
|
.await;
|
|
client
|
|
.patch_and_check(
|
|
path,
|
|
[
|
|
(DavProperty::CalDav(CalDavProperty::CalendarDescription), ""),
|
|
(DavProperty::CalDav(CalDavProperty::TimezoneId), ""),
|
|
],
|
|
)
|
|
.await;
|
|
client
|
|
.patch_and_check(
|
|
path,
|
|
[(
|
|
DavProperty::CalDav(CalDavProperty::CalendarTimezone),
|
|
TEST_VTIMEZONE_1.replace('\n', "\r\n").as_str(),
|
|
)],
|
|
)
|
|
.await;
|
|
}
|
|
DavResourceName::Card if !is_file => {
|
|
// Test 16: Change an addressbook's properties
|
|
client
|
|
.patch_and_check(
|
|
path,
|
|
[(
|
|
DavProperty::CardDav(CardDavProperty::AddressbookDescription),
|
|
"New calendar description",
|
|
)],
|
|
)
|
|
.await;
|
|
client
|
|
.patch_and_check(
|
|
path,
|
|
[(
|
|
DavProperty::CardDav(CardDavProperty::AddressbookDescription),
|
|
"",
|
|
)],
|
|
)
|
|
.await;
|
|
}
|
|
_ => (),
|
|
}
|
|
|
|
// Test 17: PROPPATCH should fail on large properties
|
|
let mut chunky_props = vec![
|
|
DavProperty::WebDav(WebDavProperty::DisplayName),
|
|
DavProperty::DeadProperty(DeadElementTag::new(
|
|
"my-chunky-dead-element".to_string(),
|
|
Some("xmlns=\"http://example.com/ns/\"".to_string()),
|
|
)),
|
|
];
|
|
if !is_file {
|
|
if resource_type == DavResourceName::Cal {
|
|
chunky_props.push(DavProperty::CalDav(CalDavProperty::CalendarDescription));
|
|
} else if resource_type == DavResourceName::Card {
|
|
chunky_props.push(DavProperty::CardDav(
|
|
CardDavProperty::AddressbookDescription,
|
|
));
|
|
}
|
|
}
|
|
let chunky_live_contents = (0..=(test.server.core.groupware.live_property_size + 1))
|
|
.map(|_| "a")
|
|
.collect::<String>();
|
|
let chunky_dead_contents =
|
|
(0..=(test.server.core.groupware.dead_property_size.unwrap() + 1))
|
|
.map(|_| "a")
|
|
.collect::<String>();
|
|
let response = client
|
|
.proppatch(
|
|
path,
|
|
chunky_props.iter().map(|prop| {
|
|
(
|
|
prop.clone(),
|
|
if matches!(prop, DavProperty::DeadProperty(_)) {
|
|
&chunky_dead_contents
|
|
} else {
|
|
&chunky_live_contents
|
|
}
|
|
.as_str(),
|
|
)
|
|
}),
|
|
[],
|
|
[],
|
|
)
|
|
.await
|
|
.into_propfind_response(None);
|
|
let props = response.properties(path);
|
|
for prop in chunky_props {
|
|
props
|
|
.get(prop)
|
|
.with_status(StatusCode::INSUFFICIENT_STORAGE)
|
|
.with_description("Property value is too long");
|
|
}
|
|
|
|
// Test 18: PROPPATCH should fail on invalid calendar property values
|
|
if !is_file && resource_type == DavResourceName::Cal {
|
|
let response = client
|
|
.proppatch(
|
|
path,
|
|
[
|
|
(
|
|
DavProperty::CalDav(CalDavProperty::TimezoneId),
|
|
"unknown/zone",
|
|
),
|
|
(
|
|
DavProperty::CalDav(CalDavProperty::CalendarTimezone),
|
|
TEST_ICAL_2,
|
|
),
|
|
],
|
|
[],
|
|
[],
|
|
)
|
|
.await
|
|
.into_propfind_response(None);
|
|
let props = response.properties(path);
|
|
props
|
|
.get(DavProperty::CalDav(CalDavProperty::TimezoneId))
|
|
.with_status(StatusCode::PRECONDITION_FAILED)
|
|
.with_description("Invalid timezone ID");
|
|
props
|
|
.get(DavProperty::CalDav(CalDavProperty::CalendarTimezone))
|
|
.with_status(StatusCode::PRECONDITION_FAILED)
|
|
.with_description("Invalid calendar timezone");
|
|
}
|
|
}
|
|
|
|
client
|
|
.request("DELETE", &test_base_path, "")
|
|
.await
|
|
.with_status(StatusCode::NO_CONTENT);
|
|
}
|
|
|
|
client.delete_default_containers().await;
|
|
client
|
|
.delete_default_containers_by_account("[email protected]")
|
|
.await;
|
|
test.assert_is_empty().await;
|
|
}
|
|
|
|
const EXPAND_REPORT_QUERY: &str = r#"<?xml version="1.0" encoding="utf-8"?>
|
|
<D:expand-property xmlns:D="DAV:"
|
|
xmlns:A="urn:ietf:params:xml:ns:caldav"
|
|
xmlns:B="urn:ietf:params:xml:ns:carddav">
|
|
<A:property name="calendar-description"/>
|
|
<B:property name="addressbook-description"/>
|
|
<D:property name="current-user-principal">
|
|
<D:property name="displayname"/>
|
|
</D:property>
|
|
<D:property name="owner">
|
|
<D:property name="displayname"/>
|
|
</D:property>
|
|
</D:expand-property>"#;
|
|
|
|
pub const ALL_DAV_PROPERTIES: &[DavProperty] = &[
|
|
DavProperty::WebDav(WebDavProperty::CreationDate),
|
|
DavProperty::WebDav(WebDavProperty::DisplayName),
|
|
DavProperty::WebDav(WebDavProperty::GetContentLanguage),
|
|
DavProperty::WebDav(WebDavProperty::GetContentLength),
|
|
DavProperty::WebDav(WebDavProperty::GetContentType),
|
|
DavProperty::WebDav(WebDavProperty::GetETag),
|
|
DavProperty::WebDav(WebDavProperty::GetLastModified),
|
|
DavProperty::WebDav(WebDavProperty::ResourceType),
|
|
DavProperty::WebDav(WebDavProperty::LockDiscovery),
|
|
DavProperty::WebDav(WebDavProperty::SupportedLock),
|
|
DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal),
|
|
DavProperty::WebDav(WebDavProperty::QuotaAvailableBytes),
|
|
DavProperty::WebDav(WebDavProperty::QuotaUsedBytes),
|
|
DavProperty::WebDav(WebDavProperty::SupportedReportSet),
|
|
DavProperty::WebDav(WebDavProperty::SyncToken),
|
|
DavProperty::WebDav(WebDavProperty::Owner),
|
|
DavProperty::WebDav(WebDavProperty::Group),
|
|
DavProperty::WebDav(WebDavProperty::SupportedPrivilegeSet),
|
|
DavProperty::WebDav(WebDavProperty::CurrentUserPrivilegeSet),
|
|
DavProperty::WebDav(WebDavProperty::Acl),
|
|
DavProperty::WebDav(WebDavProperty::AclRestrictions),
|
|
DavProperty::WebDav(WebDavProperty::InheritedAclSet),
|
|
DavProperty::WebDav(WebDavProperty::PrincipalCollectionSet),
|
|
DavProperty::WebDav(WebDavProperty::GetCTag),
|
|
DavProperty::CardDav(CardDavProperty::AddressbookDescription),
|
|
DavProperty::CardDav(CardDavProperty::SupportedAddressData),
|
|
DavProperty::CardDav(CardDavProperty::SupportedCollationSet),
|
|
DavProperty::CardDav(CardDavProperty::MaxResourceSize),
|
|
DavProperty::CalDav(CalDavProperty::CalendarDescription),
|
|
DavProperty::CalDav(CalDavProperty::CalendarTimezone),
|
|
DavProperty::CalDav(CalDavProperty::SupportedCalendarComponentSet),
|
|
DavProperty::CalDav(CalDavProperty::SupportedCalendarData),
|
|
DavProperty::CalDav(CalDavProperty::SupportedCollationSet),
|
|
DavProperty::CalDav(CalDavProperty::MaxResourceSize),
|
|
DavProperty::CalDav(CalDavProperty::MinDateTime),
|
|
DavProperty::CalDav(CalDavProperty::MaxDateTime),
|
|
DavProperty::CalDav(CalDavProperty::MaxInstances),
|
|
DavProperty::CalDav(CalDavProperty::MaxAttendeesPerInstance),
|
|
DavProperty::CalDav(CalDavProperty::TimezoneServiceSet),
|
|
DavProperty::CalDav(CalDavProperty::TimezoneId),
|
|
DavProperty::CalDav(CalDavProperty::ScheduleDefaultCalendarURL),
|
|
DavProperty::CalDav(CalDavProperty::ScheduleTag),
|
|
DavProperty::CalDav(CalDavProperty::ScheduleCalendarTransp),
|
|
DavProperty::Principal(PrincipalProperty::AlternateURISet),
|
|
DavProperty::Principal(PrincipalProperty::PrincipalURL),
|
|
DavProperty::Principal(PrincipalProperty::GroupMemberSet),
|
|
DavProperty::Principal(PrincipalProperty::GroupMembership),
|
|
DavProperty::Principal(PrincipalProperty::CalendarHomeSet),
|
|
DavProperty::Principal(PrincipalProperty::AddressbookHomeSet),
|
|
DavProperty::Principal(PrincipalProperty::PrincipalAddress),
|
|
DavProperty::Principal(PrincipalProperty::CalendarUserAddressSet),
|
|
DavProperty::Principal(PrincipalProperty::CalendarUserType),
|
|
DavProperty::Principal(PrincipalProperty::ScheduleInboxURL),
|
|
DavProperty::Principal(PrincipalProperty::ScheduleOutboxURL),
|
|
];
|