Import upstream v0.16.22, stripped
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.
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
#![warn(clippy::large_futures)]
|
||||
|
||||
use calcard::common::timezone::Tz;
|
||||
use common::DavResources;
|
||||
use percent_encoding::{AsciiSet, CONTROLS, percent_decode_str, utf8_percent_encode};
|
||||
use std::borrow::Cow;
|
||||
use types::collection::{Collection, SyncCollection};
|
||||
|
||||
pub mod cache;
|
||||
pub mod calendar;
|
||||
pub mod contact;
|
||||
pub mod file;
|
||||
pub mod scheduling;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DavResourceName {
|
||||
Card,
|
||||
Cal,
|
||||
File,
|
||||
Principal,
|
||||
Scheduling,
|
||||
}
|
||||
|
||||
pub const RFC_3986: &AsciiSet = &CONTROLS
|
||||
.add(b' ')
|
||||
.add(b'!')
|
||||
.add(b'"')
|
||||
.add(b'#')
|
||||
.add(b'$')
|
||||
.add(b'%')
|
||||
.add(b'&')
|
||||
.add(b'\'')
|
||||
.add(b'(')
|
||||
.add(b')')
|
||||
.add(b'*')
|
||||
.add(b'+')
|
||||
.add(b',')
|
||||
.add(b'/')
|
||||
.add(b':')
|
||||
.add(b';')
|
||||
.add(b'<')
|
||||
.add(b'=')
|
||||
.add(b'>')
|
||||
.add(b'?')
|
||||
.add(b'@')
|
||||
.add(b'[')
|
||||
.add(b'\\')
|
||||
.add(b']')
|
||||
.add(b'^')
|
||||
.add(b'`')
|
||||
.add(b'{')
|
||||
.add(b'|')
|
||||
.add(b'}');
|
||||
|
||||
fn is_pchar(byte: u8) -> bool {
|
||||
matches!(byte,
|
||||
b'A'..=b'Z'
|
||||
| b'a'..=b'z'
|
||||
| b'0'..=b'9'
|
||||
| b'-'
|
||||
| b'.'
|
||||
| b'_'
|
||||
| b'~'
|
||||
| b'!'
|
||||
| b'$'
|
||||
| b'&'
|
||||
| b'\''
|
||||
| b'('
|
||||
| b')'
|
||||
| b'*'
|
||||
| b'+'
|
||||
| b','
|
||||
| b';'
|
||||
| b'='
|
||||
| b':'
|
||||
| b'@')
|
||||
}
|
||||
|
||||
pub fn is_uri_segment(name: &str) -> bool {
|
||||
let mut bytes = name.as_bytes().iter();
|
||||
|
||||
while let Some(&byte) = bytes.next() {
|
||||
if byte == b'%' {
|
||||
if !bytes.next().is_some_and(u8::is_ascii_hexdigit)
|
||||
|| !bytes.next().is_some_and(u8::is_ascii_hexdigit)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
} else if !is_pchar(byte) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn encode_path_segment(name: &str) -> Cow<'_, str> {
|
||||
if is_uri_segment(name) {
|
||||
Cow::Borrowed(name)
|
||||
} else {
|
||||
utf8_percent_encode(name, RFC_3986).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DestroyArchive<T>(pub T);
|
||||
|
||||
impl DavResourceName {
|
||||
pub fn parse(service: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(service.as_bytes(),
|
||||
"card" => DavResourceName::Card,
|
||||
"cal" => DavResourceName::Cal,
|
||||
"file" => DavResourceName::File,
|
||||
"pal" => DavResourceName::Principal,
|
||||
"itip" => DavResourceName::Scheduling,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn base_path(&self) -> &'static str {
|
||||
match self {
|
||||
DavResourceName::Card => "/dav/card",
|
||||
DavResourceName::Cal => "/dav/cal",
|
||||
DavResourceName::File => "/dav/file",
|
||||
DavResourceName::Principal => "/dav/pal",
|
||||
DavResourceName::Scheduling => "/dav/itip",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collection_path(&self) -> &'static str {
|
||||
match self {
|
||||
DavResourceName::Card => "/dav/card/",
|
||||
DavResourceName::Cal => "/dav/cal/",
|
||||
DavResourceName::File => "/dav/file/",
|
||||
DavResourceName::Principal => "/dav/pal/",
|
||||
DavResourceName::Scheduling => "/dav/itip/",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
DavResourceName::Card => "CardDAV",
|
||||
DavResourceName::Cal => "CalDAV",
|
||||
DavResourceName::File => "WebDAV",
|
||||
DavResourceName::Principal => "Principal",
|
||||
DavResourceName::Scheduling => "Scheduling",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DavResourceName> for Collection {
|
||||
fn from(value: DavResourceName) -> Self {
|
||||
match value {
|
||||
DavResourceName::Card => Collection::AddressBook,
|
||||
DavResourceName::Cal => Collection::Calendar,
|
||||
DavResourceName::File => Collection::FileNode,
|
||||
DavResourceName::Principal => Collection::Principal,
|
||||
DavResourceName::Scheduling => Collection::CalendarEventNotification,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Collection> for DavResourceName {
|
||||
fn from(value: Collection) -> Self {
|
||||
match value {
|
||||
Collection::AddressBook => DavResourceName::Card,
|
||||
Collection::Calendar => DavResourceName::Cal,
|
||||
Collection::FileNode => DavResourceName::File,
|
||||
Collection::Principal => DavResourceName::Principal,
|
||||
Collection::CalendarEventNotification => DavResourceName::Scheduling,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SyncCollection> for DavResourceName {
|
||||
fn from(value: SyncCollection) -> Self {
|
||||
match value {
|
||||
SyncCollection::AddressBook => DavResourceName::Card,
|
||||
SyncCollection::Calendar => DavResourceName::Cal,
|
||||
SyncCollection::FileNode => DavResourceName::File,
|
||||
SyncCollection::CalendarEventNotification => DavResourceName::Scheduling,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DavCalendarResource {
|
||||
fn calendar_default_tz(&self, calendar_id: u32, account_id: u32) -> Option<Tz>;
|
||||
}
|
||||
|
||||
impl DavCalendarResource for DavResources {
|
||||
fn calendar_default_tz(&self, calendar_id: u32, account_id: u32) -> Option<Tz> {
|
||||
self.container_resource_by_id(calendar_id)
|
||||
.and_then(|c| c.calendar_preferences(account_id))
|
||||
.map(|p| p.tz)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn strip_mailto_scheme(value: &str) -> &str {
|
||||
value
|
||||
.split_once(':')
|
||||
.filter(|(scheme, _)| scheme.eq_ignore_ascii_case("mailto"))
|
||||
.map_or(value, |(_, address)| address.trim())
|
||||
}
|
||||
|
||||
pub fn decode_mailto_address(value: &str) -> Cow<'_, str> {
|
||||
match value.split_once(':') {
|
||||
Some((scheme, address)) if scheme.eq_ignore_ascii_case("mailto") => {
|
||||
let address = address.trim();
|
||||
let address = address.split_once('?').map_or(address, |(to, _)| to);
|
||||
percent_decode_str(address).decode_utf8_lossy()
|
||||
}
|
||||
_ => Cow::Borrowed(value),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_addr_spec(value: &str) -> Option<&str> {
|
||||
value
|
||||
.rsplit_once('<')
|
||||
.and_then(|(_, rest)| rest.split_once('>'))
|
||||
.map(|(addr, _)| addr.trim())
|
||||
.filter(|addr| !addr.is_empty())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn path_segments_from_uris_are_preserved() {
|
||||
for name in [
|
||||
"readme.txt",
|
||||
"My%20Folder",
|
||||
"%C3%9Cnterlagen.txt",
|
||||
"file(1).txt",
|
||||
"a+b.txt",
|
||||
"Q&A.txt",
|
||||
"it's.txt",
|
||||
"[email protected]",
|
||||
"a:b.txt",
|
||||
"notes;v=2,rev=3!$*=.txt",
|
||||
"~backup_1-2.txt",
|
||||
] {
|
||||
assert!(is_uri_segment(name), "{name:?}");
|
||||
assert_eq!(encode_path_segment(name), name);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_segments_from_names_are_encoded() {
|
||||
for (name, expected) in [
|
||||
("My Folder", "My%20Folder"),
|
||||
("Ünterlagen.txt", "%C3%9Cnterlagen.txt"),
|
||||
("Ünterlagen 2026.txt", "%C3%9Cnterlagen%202026.txt"),
|
||||
("100%", "100%25"),
|
||||
("100%2", "100%252"),
|
||||
("100%zz", "100%25zz"),
|
||||
("a/b.txt", "a%2Fb.txt"),
|
||||
("a<b>c.txt", "a%3Cb%3Ec.txt"),
|
||||
("a\"b#c?d.txt", "a%22b%23c%3Fd.txt"),
|
||||
("a\tb.txt", "a%09b.txt"),
|
||||
] {
|
||||
assert!(!is_uri_segment(name), "{name:?}");
|
||||
assert_eq!(encode_path_segment(name), expected, "{name:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoded_path_segments_are_stable() {
|
||||
for name in [
|
||||
"My Folder",
|
||||
"Ünterlagen 2026.txt",
|
||||
"100%",
|
||||
"a/b.txt",
|
||||
"file(1).txt",
|
||||
] {
|
||||
let encoded = encode_path_segment(name).into_owned();
|
||||
assert!(is_uri_segment(&encoded), "{encoded:?}");
|
||||
assert_eq!(encode_path_segment(&encoded), encoded, "{name:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user