mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-10-07 19:15:47 +08:00
CardDAV addressbook-query
This commit is contained in:
parent
a06c94d45d
commit
b9d93730c7
17 changed files with 883 additions and 600 deletions
|
@ -534,6 +534,12 @@ impl DavResources {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn children(&self, parent_id: u32) -> impl Iterator<Item = &DavResource> {
|
||||
self.paths
|
||||
.iter()
|
||||
.filter(move |item| item.parent_id.is_some_and(|id| id == parent_id))
|
||||
}
|
||||
|
||||
pub fn is_ancestor_of(&self, ancestor: u32, descendant: u32) -> bool {
|
||||
let ancestor = &self.paths.by_id(ancestor).unwrap().name;
|
||||
let descendant = &self.paths.by_id(descendant).unwrap().name;
|
||||
|
|
|
@ -45,34 +45,39 @@
|
|||
},
|
||||
"value": {
|
||||
"ICalendar": {
|
||||
"component_type": "VCalendar",
|
||||
"entries": [
|
||||
"components": [
|
||||
{
|
||||
"name": {
|
||||
"type": "Prodid"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
"component_type": "VCalendar",
|
||||
"entries": [
|
||||
{
|
||||
"type": "Text",
|
||||
"data": "-//Example Corp.//CalDAV Client//EN"
|
||||
"name": {
|
||||
"type": "Prodid"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "Text",
|
||||
"data": "-//Example Corp.//CalDAV Client//EN"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Version"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "Text",
|
||||
"data": "2.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"component_ids": [
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Version"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "Text",
|
||||
"data": "2.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"component_type": "VTimezone",
|
||||
"entries": [
|
||||
|
@ -111,244 +116,246 @@
|
|||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
"component_ids": [
|
||||
2,
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"component_type": "Standard",
|
||||
"entries": [
|
||||
{
|
||||
"component_type": "Standard",
|
||||
"entries": [
|
||||
"name": {
|
||||
"type": "Dtstart"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"name": {
|
||||
"type": "Dtstart"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": 1967,
|
||||
"month": 10,
|
||||
"day": 29,
|
||||
"hour": 2,
|
||||
"minute": 0,
|
||||
"second": 0,
|
||||
"tz_hour": null,
|
||||
"tz_minute": null,
|
||||
"tz_minus": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Rrule"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "RecurrenceRule",
|
||||
"data": {
|
||||
"freq": "Yearly",
|
||||
"until": null,
|
||||
"count": null,
|
||||
"interval": null,
|
||||
"bysecond": [],
|
||||
"byminute": [],
|
||||
"byhour": [],
|
||||
"byday": [
|
||||
{
|
||||
"ordwk": -1,
|
||||
"weekday": "Sunday"
|
||||
}
|
||||
],
|
||||
"bymonthday": [],
|
||||
"byyearday": [],
|
||||
"byweekno": [],
|
||||
"bymonth": [
|
||||
10
|
||||
],
|
||||
"bysetpos": [],
|
||||
"wkst": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzoffsetfrom"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
"hour": null,
|
||||
"minute": null,
|
||||
"second": null,
|
||||
"tz_hour": 4,
|
||||
"tz_minute": 0,
|
||||
"tz_minus": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzoffsetto"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
"hour": null,
|
||||
"minute": null,
|
||||
"second": null,
|
||||
"tz_hour": 5,
|
||||
"tz_minute": 0,
|
||||
"tz_minus": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzname"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "Text",
|
||||
"data": "Eastern Standard Time (US & Canada)"
|
||||
}
|
||||
]
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": 1967,
|
||||
"month": 10,
|
||||
"day": 29,
|
||||
"hour": 2,
|
||||
"minute": 0,
|
||||
"second": 0,
|
||||
"tz_hour": null,
|
||||
"tz_minute": null,
|
||||
"tz_minus": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"components": []
|
||||
]
|
||||
},
|
||||
{
|
||||
"component_type": "Daylight",
|
||||
"entries": [
|
||||
"name": {
|
||||
"type": "Rrule"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"name": {
|
||||
"type": "Dtstart"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": 1987,
|
||||
"month": 4,
|
||||
"day": 5,
|
||||
"hour": 2,
|
||||
"minute": 0,
|
||||
"second": 0,
|
||||
"tz_hour": null,
|
||||
"tz_minute": null,
|
||||
"tz_minus": false
|
||||
"type": "RecurrenceRule",
|
||||
"data": {
|
||||
"freq": "Yearly",
|
||||
"until": null,
|
||||
"count": null,
|
||||
"interval": null,
|
||||
"bysecond": [],
|
||||
"byminute": [],
|
||||
"byhour": [],
|
||||
"byday": [
|
||||
{
|
||||
"ordwk": -1,
|
||||
"weekday": "Sunday"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Rrule"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "RecurrenceRule",
|
||||
"data": {
|
||||
"freq": "Yearly",
|
||||
"until": null,
|
||||
"count": null,
|
||||
"interval": null,
|
||||
"bysecond": [],
|
||||
"byminute": [],
|
||||
"byhour": [],
|
||||
"byday": [
|
||||
{
|
||||
"ordwk": 1,
|
||||
"weekday": "Sunday"
|
||||
}
|
||||
],
|
||||
"bymonthday": [],
|
||||
"byyearday": [],
|
||||
"byweekno": [],
|
||||
"bymonth": [
|
||||
4
|
||||
],
|
||||
"bysetpos": [],
|
||||
"wkst": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzoffsetfrom"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
"hour": null,
|
||||
"minute": null,
|
||||
"second": null,
|
||||
"tz_hour": 5,
|
||||
"tz_minute": 0,
|
||||
"tz_minus": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzoffsetto"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
"hour": null,
|
||||
"minute": null,
|
||||
"second": null,
|
||||
"tz_hour": 4,
|
||||
"tz_minute": 0,
|
||||
"tz_minus": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzname"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "Text",
|
||||
"data": "Eastern Daylight Time (US & Canada)"
|
||||
}
|
||||
]
|
||||
],
|
||||
"bymonthday": [],
|
||||
"byyearday": [],
|
||||
"byweekno": [],
|
||||
"bymonth": [
|
||||
10
|
||||
],
|
||||
"bysetpos": [],
|
||||
"wkst": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"components": []
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzoffsetfrom"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
"hour": null,
|
||||
"minute": null,
|
||||
"second": null,
|
||||
"tz_hour": 4,
|
||||
"tz_minute": 0,
|
||||
"tz_minus": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzoffsetto"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
"hour": null,
|
||||
"minute": null,
|
||||
"second": null,
|
||||
"tz_hour": 5,
|
||||
"tz_minute": 0,
|
||||
"tz_minus": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzname"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "Text",
|
||||
"data": "Eastern Standard Time (US & Canada)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"component_ids": []
|
||||
},
|
||||
{
|
||||
"component_type": "Daylight",
|
||||
"entries": [
|
||||
{
|
||||
"name": {
|
||||
"type": "Dtstart"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": 1987,
|
||||
"month": 4,
|
||||
"day": 5,
|
||||
"hour": 2,
|
||||
"minute": 0,
|
||||
"second": 0,
|
||||
"tz_hour": null,
|
||||
"tz_minute": null,
|
||||
"tz_minus": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Rrule"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "RecurrenceRule",
|
||||
"data": {
|
||||
"freq": "Yearly",
|
||||
"until": null,
|
||||
"count": null,
|
||||
"interval": null,
|
||||
"bysecond": [],
|
||||
"byminute": [],
|
||||
"byhour": [],
|
||||
"byday": [
|
||||
{
|
||||
"ordwk": 1,
|
||||
"weekday": "Sunday"
|
||||
}
|
||||
],
|
||||
"bymonthday": [],
|
||||
"byyearday": [],
|
||||
"byweekno": [],
|
||||
"bymonth": [
|
||||
4
|
||||
],
|
||||
"bysetpos": [],
|
||||
"wkst": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzoffsetfrom"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
"hour": null,
|
||||
"minute": null,
|
||||
"second": null,
|
||||
"tz_hour": 5,
|
||||
"tz_minute": 0,
|
||||
"tz_minus": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzoffsetto"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
"hour": null,
|
||||
"minute": null,
|
||||
"second": null,
|
||||
"tz_hour": 4,
|
||||
"tz_minute": 0,
|
||||
"tz_minus": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzname"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "Text",
|
||||
"data": "Eastern Daylight Time (US & Canada)"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"component_ids": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -45,34 +45,39 @@
|
|||
},
|
||||
"value": {
|
||||
"ICalendar": {
|
||||
"component_type": "VCalendar",
|
||||
"entries": [
|
||||
"components": [
|
||||
{
|
||||
"name": {
|
||||
"type": "Prodid"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
"component_type": "VCalendar",
|
||||
"entries": [
|
||||
{
|
||||
"type": "Text",
|
||||
"data": "-//Example Corp.//CalDAV Client//EN"
|
||||
"name": {
|
||||
"type": "Prodid"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "Text",
|
||||
"data": "-//Example Corp.//CalDAV Client//EN"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Version"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "Text",
|
||||
"data": "2.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"component_ids": [
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Version"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "Text",
|
||||
"data": "2.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"component_type": "VTimezone",
|
||||
"entries": [
|
||||
|
@ -111,244 +116,246 @@
|
|||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
"component_ids": [
|
||||
2,
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"component_type": "Standard",
|
||||
"entries": [
|
||||
{
|
||||
"component_type": "Standard",
|
||||
"entries": [
|
||||
"name": {
|
||||
"type": "Dtstart"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"name": {
|
||||
"type": "Dtstart"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": 1967,
|
||||
"month": 10,
|
||||
"day": 29,
|
||||
"hour": 2,
|
||||
"minute": 0,
|
||||
"second": 0,
|
||||
"tz_hour": null,
|
||||
"tz_minute": null,
|
||||
"tz_minus": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Rrule"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "RecurrenceRule",
|
||||
"data": {
|
||||
"freq": "Yearly",
|
||||
"until": null,
|
||||
"count": null,
|
||||
"interval": null,
|
||||
"bysecond": [],
|
||||
"byminute": [],
|
||||
"byhour": [],
|
||||
"byday": [
|
||||
{
|
||||
"ordwk": -1,
|
||||
"weekday": "Sunday"
|
||||
}
|
||||
],
|
||||
"bymonthday": [],
|
||||
"byyearday": [],
|
||||
"byweekno": [],
|
||||
"bymonth": [
|
||||
10
|
||||
],
|
||||
"bysetpos": [],
|
||||
"wkst": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzoffsetfrom"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
"hour": null,
|
||||
"minute": null,
|
||||
"second": null,
|
||||
"tz_hour": 4,
|
||||
"tz_minute": 0,
|
||||
"tz_minus": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzoffsetto"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
"hour": null,
|
||||
"minute": null,
|
||||
"second": null,
|
||||
"tz_hour": 5,
|
||||
"tz_minute": 0,
|
||||
"tz_minus": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzname"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "Text",
|
||||
"data": "Eastern Standard Time (US & Canada)"
|
||||
}
|
||||
]
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": 1967,
|
||||
"month": 10,
|
||||
"day": 29,
|
||||
"hour": 2,
|
||||
"minute": 0,
|
||||
"second": 0,
|
||||
"tz_hour": null,
|
||||
"tz_minute": null,
|
||||
"tz_minus": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"components": []
|
||||
]
|
||||
},
|
||||
{
|
||||
"component_type": "Daylight",
|
||||
"entries": [
|
||||
"name": {
|
||||
"type": "Rrule"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"name": {
|
||||
"type": "Dtstart"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": 1987,
|
||||
"month": 4,
|
||||
"day": 5,
|
||||
"hour": 2,
|
||||
"minute": 0,
|
||||
"second": 0,
|
||||
"tz_hour": null,
|
||||
"tz_minute": null,
|
||||
"tz_minus": false
|
||||
"type": "RecurrenceRule",
|
||||
"data": {
|
||||
"freq": "Yearly",
|
||||
"until": null,
|
||||
"count": null,
|
||||
"interval": null,
|
||||
"bysecond": [],
|
||||
"byminute": [],
|
||||
"byhour": [],
|
||||
"byday": [
|
||||
{
|
||||
"ordwk": -1,
|
||||
"weekday": "Sunday"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Rrule"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "RecurrenceRule",
|
||||
"data": {
|
||||
"freq": "Yearly",
|
||||
"until": null,
|
||||
"count": null,
|
||||
"interval": null,
|
||||
"bysecond": [],
|
||||
"byminute": [],
|
||||
"byhour": [],
|
||||
"byday": [
|
||||
{
|
||||
"ordwk": 1,
|
||||
"weekday": "Sunday"
|
||||
}
|
||||
],
|
||||
"bymonthday": [],
|
||||
"byyearday": [],
|
||||
"byweekno": [],
|
||||
"bymonth": [
|
||||
4
|
||||
],
|
||||
"bysetpos": [],
|
||||
"wkst": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzoffsetfrom"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
"hour": null,
|
||||
"minute": null,
|
||||
"second": null,
|
||||
"tz_hour": 5,
|
||||
"tz_minute": 0,
|
||||
"tz_minus": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzoffsetto"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
"hour": null,
|
||||
"minute": null,
|
||||
"second": null,
|
||||
"tz_hour": 4,
|
||||
"tz_minute": 0,
|
||||
"tz_minus": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzname"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "Text",
|
||||
"data": "Eastern Daylight Time (US & Canada)"
|
||||
}
|
||||
]
|
||||
],
|
||||
"bymonthday": [],
|
||||
"byyearday": [],
|
||||
"byweekno": [],
|
||||
"bymonth": [
|
||||
10
|
||||
],
|
||||
"bysetpos": [],
|
||||
"wkst": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"components": []
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzoffsetfrom"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
"hour": null,
|
||||
"minute": null,
|
||||
"second": null,
|
||||
"tz_hour": 4,
|
||||
"tz_minute": 0,
|
||||
"tz_minus": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzoffsetto"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
"hour": null,
|
||||
"minute": null,
|
||||
"second": null,
|
||||
"tz_hour": 5,
|
||||
"tz_minute": 0,
|
||||
"tz_minus": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzname"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "Text",
|
||||
"data": "Eastern Standard Time (US & Canada)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"component_ids": []
|
||||
},
|
||||
{
|
||||
"component_type": "Daylight",
|
||||
"entries": [
|
||||
{
|
||||
"name": {
|
||||
"type": "Dtstart"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": 1987,
|
||||
"month": 4,
|
||||
"day": 5,
|
||||
"hour": 2,
|
||||
"minute": 0,
|
||||
"second": 0,
|
||||
"tz_hour": null,
|
||||
"tz_minute": null,
|
||||
"tz_minus": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Rrule"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "RecurrenceRule",
|
||||
"data": {
|
||||
"freq": "Yearly",
|
||||
"until": null,
|
||||
"count": null,
|
||||
"interval": null,
|
||||
"bysecond": [],
|
||||
"byminute": [],
|
||||
"byhour": [],
|
||||
"byday": [
|
||||
{
|
||||
"ordwk": 1,
|
||||
"weekday": "Sunday"
|
||||
}
|
||||
],
|
||||
"bymonthday": [],
|
||||
"byyearday": [],
|
||||
"byweekno": [],
|
||||
"bymonth": [
|
||||
4
|
||||
],
|
||||
"bysetpos": [],
|
||||
"wkst": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzoffsetfrom"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
"hour": null,
|
||||
"minute": null,
|
||||
"second": null,
|
||||
"tz_hour": 5,
|
||||
"tz_minute": 0,
|
||||
"tz_minus": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzoffsetto"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "PartialDateTime",
|
||||
"data": {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
"hour": null,
|
||||
"minute": null,
|
||||
"second": null,
|
||||
"tz_hour": 4,
|
||||
"tz_minute": 0,
|
||||
"tz_minus": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"type": "Tzname"
|
||||
},
|
||||
"params": [],
|
||||
"values": [
|
||||
{
|
||||
"type": "Text",
|
||||
"data": "Eastern Daylight Time (US & Canada)"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"component_ids": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"type": "WebDav",
|
||||
"type": "Principal",
|
||||
"data": {
|
||||
"type": "GroupMembership"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"type": "Addressbook",
|
||||
"type": "AddressbookQuery",
|
||||
"properties": {
|
||||
"type": "Prop",
|
||||
"data": [
|
||||
|
@ -55,12 +55,6 @@
|
|||
]
|
||||
},
|
||||
"filters": [
|
||||
{
|
||||
"type": "AnyOf"
|
||||
},
|
||||
{
|
||||
"type": "AnyOf"
|
||||
},
|
||||
{
|
||||
"type": "Property",
|
||||
"comp": null,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"type": "Addressbook",
|
||||
"type": "AddressbookQuery",
|
||||
"properties": {
|
||||
"type": "Prop",
|
||||
"data": [
|
||||
|
@ -55,9 +55,6 @@
|
|||
]
|
||||
},
|
||||
"filters": [
|
||||
{
|
||||
"type": "AnyOf"
|
||||
},
|
||||
{
|
||||
"type": "AnyOf"
|
||||
},
|
||||
|
@ -81,9 +78,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "AnyOf"
|
||||
},
|
||||
{
|
||||
"type": "Property",
|
||||
"comp": null,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"type": "Addressbook",
|
||||
"type": "AddressbookQuery",
|
||||
"properties": {
|
||||
"type": "Prop",
|
||||
"data": [
|
||||
|
@ -12,9 +12,6 @@
|
|||
]
|
||||
},
|
||||
"filters": [
|
||||
{
|
||||
"type": "AnyOf"
|
||||
},
|
||||
{
|
||||
"type": "AnyOf"
|
||||
},
|
||||
|
|
|
@ -12,6 +12,7 @@ pub mod schema;
|
|||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct RequestHeaders<'x> {
|
||||
pub uri: &'x str,
|
||||
pub base_uri: Option<&'x str>,
|
||||
pub depth: Depth,
|
||||
pub timeout: Timeout,
|
||||
pub content_type: Option<&'x str>,
|
||||
|
|
|
@ -10,6 +10,7 @@ impl<'x> RequestHeaders<'x> {
|
|||
pub fn new(uri: &'x str) -> Self {
|
||||
RequestHeaders {
|
||||
uri,
|
||||
base_uri: base_uri(uri),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -88,39 +89,8 @@ impl<'x> RequestHeaders<'x> {
|
|||
false
|
||||
}
|
||||
|
||||
pub fn base_uri(&self) -> Option<&'x str> {
|
||||
// From a path ../dav/collection/account/..
|
||||
// returns ../dav/collection/account without the trailing slash
|
||||
|
||||
let uri = self.uri.as_bytes();
|
||||
let mut found_dav = false;
|
||||
let mut last_idx = 0;
|
||||
let mut sep_count = 0;
|
||||
|
||||
for (idx, ch) in uri.iter().enumerate() {
|
||||
if *ch == b'/' {
|
||||
if !found_dav {
|
||||
found_dav = uri.get(idx + 1..idx + 5).is_some_and(|s| s == b"dav/");
|
||||
} else if found_dav {
|
||||
if sep_count == 2 {
|
||||
break;
|
||||
}
|
||||
sep_count += 1;
|
||||
}
|
||||
}
|
||||
last_idx = idx;
|
||||
}
|
||||
|
||||
if sep_count == 2 {
|
||||
uri.get(..last_idx + 1)
|
||||
.map(|uri| std::str::from_utf8(uri).unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_to_base_uri(&self, path: &str) -> String {
|
||||
let base_uri = self.base_uri().unwrap_or_default();
|
||||
let base_uri = self.base_uri.unwrap_or_default();
|
||||
format!("{base_uri}/{path}")
|
||||
}
|
||||
|
||||
|
@ -292,6 +262,37 @@ impl<'x> RequestHeaders<'x> {
|
|||
}
|
||||
}
|
||||
|
||||
fn base_uri(uri: &str) -> Option<&str> {
|
||||
// From a path ../dav/collection/account/..
|
||||
// returns ../dav/collection/account without the trailing slash
|
||||
|
||||
let uri = uri.as_bytes();
|
||||
let mut found_dav = false;
|
||||
let mut last_idx = 0;
|
||||
let mut sep_count = 0;
|
||||
|
||||
for (idx, ch) in uri.iter().enumerate() {
|
||||
if *ch == b'/' {
|
||||
if !found_dav {
|
||||
found_dav = uri.get(idx + 1..idx + 5).is_some_and(|s| s == b"dav/");
|
||||
} else if found_dav {
|
||||
if sep_count == 2 {
|
||||
break;
|
||||
}
|
||||
sep_count += 1;
|
||||
}
|
||||
}
|
||||
last_idx = idx;
|
||||
}
|
||||
|
||||
if sep_count == 2 {
|
||||
uri.get(..last_idx + 1)
|
||||
.map(|uri| std::str::from_utf8(uri).unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Depth {
|
||||
pub fn parse(value: &str) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
|
@ -358,7 +359,7 @@ mod tests {
|
|||
("/dav/collection/account/", Some("/dav/collection/account")),
|
||||
("/dav/collection/account", Some("/dav/collection/account")),
|
||||
] {
|
||||
assert_eq!(RequestHeaders::new(uri).base_uri(), expected_base);
|
||||
assert_eq!(RequestHeaders::new(uri).base_uri, expected_base);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -275,7 +275,9 @@ impl DavParser for AddressbookQuery {
|
|||
ns: Namespace::CardDav,
|
||||
element: Element::Filter,
|
||||
} if depth == 1 => {
|
||||
aq.filters.push(Filter::parse(raw)?);
|
||||
if let Some(filter) = Filter::parse(raw)? {
|
||||
aq.filters.push(filter);
|
||||
}
|
||||
depth += 1;
|
||||
}
|
||||
NamedElement {
|
||||
|
@ -292,19 +294,22 @@ impl DavParser for AddressbookQuery {
|
|||
ns: Namespace::CardDav,
|
||||
element: Element::PropFilter,
|
||||
} if depth == 2 => {
|
||||
let mut filter = Filter::AnyOf;
|
||||
let mut filter = None;
|
||||
for attribute in raw.attributes::<VCardPropertyWithGroup>() {
|
||||
match attribute? {
|
||||
Attribute::Name(name) => {
|
||||
property = Some(name);
|
||||
}
|
||||
Attribute::TestAllOf(all_of) => {
|
||||
filter = if all_of { Filter::AllOf } else { Filter::AnyOf };
|
||||
filter =
|
||||
(if all_of { Filter::AllOf } else { Filter::AnyOf }).into();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
aq.filters.push(filter);
|
||||
if let Some(filter) = filter {
|
||||
aq.filters.push(filter);
|
||||
}
|
||||
depth += 1;
|
||||
}
|
||||
NamedElement {
|
||||
|
@ -542,14 +547,14 @@ impl<A, B, C> Filter<A, B, C> {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse(raw: RawElement<'_>) -> crate::parser::Result<Self> {
|
||||
fn parse(raw: RawElement<'_>) -> crate::parser::Result<Option<Self>> {
|
||||
for attribute in raw.attributes::<String>() {
|
||||
if let Attribute::TestAllOf(all_of) = attribute? {
|
||||
return Ok(if all_of { Filter::AllOf } else { Filter::AnyOf });
|
||||
return Ok(Some(if all_of { Filter::AllOf } else { Filter::AnyOf }));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Filter::AnyOf)
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
*/
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use request::TextMatch;
|
||||
pub mod property;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
@ -1413,3 +1415,14 @@ impl YesNo {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TextMatch {
|
||||
pub fn matches(&self, text: &str) -> bool {
|
||||
(match self.match_type {
|
||||
MatchType::Equals => text == self.value,
|
||||
MatchType::Contains => text.contains(&self.value),
|
||||
MatchType::StartsWith => text.starts_with(&self.value),
|
||||
MatchType::EndsWith => text.ends_with(&self.value),
|
||||
}) ^ self.negate
|
||||
}
|
||||
}
|
||||
|
|
|
@ -295,6 +295,11 @@ impl DavProperty {
|
|||
| DavProperty::WebDav(WebDavProperty::GetContentType)
|
||||
| DavProperty::WebDav(WebDavProperty::SupportedReportSet)
|
||||
| DavProperty::DeadProperty(_)
|
||||
| DavProperty::CardDav(CardDavProperty::AddressData(_))
|
||||
| DavProperty::CardDav(CardDavProperty::AddressbookDescription)
|
||||
| DavProperty::CardDav(CardDavProperty::SupportedAddressData)
|
||||
| DavProperty::CardDav(CardDavProperty::SupportedCollationSet)
|
||||
| DavProperty::CardDav(CardDavProperty::MaxResourceSize),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,28 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use calcard::vcard::{
|
||||
ArchivedVCard, ArchivedVCardEntry, ArchivedVCardParameter, VCardParameterName,
|
||||
};
|
||||
use common::{Server, auth::AccessToken};
|
||||
use dav_proto::{RequestHeaders, schema::request::AddressbookQuery};
|
||||
use dav_proto::{
|
||||
RequestHeaders,
|
||||
schema::request::{AddressbookQuery, Filter, FilterOp, VCardPropertyWithGroup},
|
||||
};
|
||||
use groupware::hierarchy::DavHierarchy;
|
||||
use http_proto::HttpResponse;
|
||||
use hyper::StatusCode;
|
||||
use jmap_proto::types::{acl::Acl, collection::Collection};
|
||||
use trc::AddContext;
|
||||
|
||||
use crate::{
|
||||
DavError,
|
||||
common::{
|
||||
AddressbookFilter, DavQuery,
|
||||
propfind::{PropFindItem, PropFindRequestHandler},
|
||||
uri::DavUriResource,
|
||||
},
|
||||
};
|
||||
|
||||
pub(crate) trait CardQueryRequestHandler: Sync + Send {
|
||||
fn handle_card_query_request(
|
||||
|
@ -24,6 +43,155 @@ impl CardQueryRequestHandler for Server {
|
|||
headers: RequestHeaders<'_>,
|
||||
request: AddressbookQuery,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
todo!()
|
||||
// Validate URI
|
||||
let resource_ = self
|
||||
.validate_uri(access_token, headers.uri)
|
||||
.await?
|
||||
.into_owned_uri()?;
|
||||
let account_id = resource_.account_id;
|
||||
let resources = self
|
||||
.fetch_dav_resources(account_id, Collection::AddressBook)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
let resource = resources
|
||||
.paths
|
||||
.by_name(
|
||||
resource_
|
||||
.resource
|
||||
.ok_or(DavError::Code(StatusCode::METHOD_NOT_ALLOWED))?,
|
||||
)
|
||||
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
||||
if !resource.is_container {
|
||||
return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED));
|
||||
}
|
||||
|
||||
// Obtain shared ids
|
||||
let shared_ids = if !access_token.is_member(account_id) {
|
||||
self.shared_containers(
|
||||
access_token,
|
||||
account_id,
|
||||
Collection::AddressBook,
|
||||
Acl::ReadItems,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.into()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Obtain document ids in folder
|
||||
let mut items = Vec::with_capacity(16);
|
||||
let base_uri = headers.base_uri.unwrap_or_default();
|
||||
for resource in resources.children(resource.document_id) {
|
||||
if shared_ids
|
||||
.as_ref()
|
||||
.is_none_or(|ids| ids.contains(resource.document_id))
|
||||
{
|
||||
items.push(PropFindItem::new(base_uri, account_id, resource));
|
||||
}
|
||||
}
|
||||
|
||||
self.handle_dav_query(
|
||||
access_token,
|
||||
DavQuery::addressbook_query(request, items, headers),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn vcard_query(card: &ArchivedVCard, filters: &AddressbookFilter) -> bool {
|
||||
let mut is_all = true;
|
||||
let mut matches_one = false;
|
||||
|
||||
for filter in filters {
|
||||
match filter {
|
||||
Filter::AnyOf => {
|
||||
is_all = false;
|
||||
}
|
||||
Filter::AllOf => {
|
||||
is_all = true;
|
||||
}
|
||||
Filter::Property { prop, op, .. } => {
|
||||
let result = if let Some(entry) = find_property(card, prop) {
|
||||
match op {
|
||||
FilterOp::Exists => true,
|
||||
FilterOp::Undefined => false,
|
||||
FilterOp::TextMatch(text_match) => {
|
||||
let mut matched_any = false;
|
||||
|
||||
for value in entry.values.iter() {
|
||||
if let Some(text) = value.as_text() {
|
||||
if text_match.matches(&text.to_lowercase()) {
|
||||
matched_any = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matched_any
|
||||
}
|
||||
FilterOp::TimeRange(_) => false,
|
||||
}
|
||||
} else {
|
||||
matches!(op, FilterOp::Undefined)
|
||||
};
|
||||
|
||||
if result {
|
||||
matches_one = true;
|
||||
} else if is_all {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Filter::Parameter {
|
||||
prop, param, op, ..
|
||||
} => {
|
||||
let result = if let Some(entry) =
|
||||
find_property(card, prop).and_then(|entry| find_parameter(entry, param))
|
||||
{
|
||||
match op {
|
||||
FilterOp::Exists => true,
|
||||
FilterOp::Undefined => false,
|
||||
FilterOp::TextMatch(text_match) => {
|
||||
if let Some(text) = entry.as_text() {
|
||||
text_match.matches(&text.to_lowercase())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
FilterOp::TimeRange(_) => false,
|
||||
}
|
||||
} else {
|
||||
matches!(op, FilterOp::Undefined)
|
||||
};
|
||||
|
||||
if result {
|
||||
matches_one = true;
|
||||
} else if is_all {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Filter::Component { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
is_all || matches_one
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn find_property<'x>(
|
||||
card: &'x ArchivedVCard,
|
||||
prop: &VCardPropertyWithGroup,
|
||||
) -> Option<&'x ArchivedVCardEntry> {
|
||||
card.entries
|
||||
.iter()
|
||||
.find(|entry| entry.name == prop.name && entry.group == prop.group)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn find_parameter<'x>(
|
||||
entry: &'x ArchivedVCardEntry,
|
||||
name: &VCardParameterName,
|
||||
) -> Option<&'x ArchivedVCardParameter> {
|
||||
entry.params.iter().find(|param| param.matches_name(name))
|
||||
}
|
||||
|
|
|
@ -251,7 +251,7 @@ impl CardUpdateRequestHandler for Server {
|
|||
account_id,
|
||||
parent.document_id,
|
||||
vcard.uid(),
|
||||
headers.base_uri().unwrap_or_default(),
|
||||
headers.base_uri.unwrap_or_default(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
|
@ -4,12 +4,19 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use calcard::{
|
||||
icalendar::{ICalendarComponentType, ICalendarParameterName, ICalendarProperty},
|
||||
vcard::VCardParameterName,
|
||||
};
|
||||
use dav_proto::{
|
||||
Depth, RequestHeaders, Return,
|
||||
schema::{
|
||||
Namespace,
|
||||
property::{ReportSet, ResourceType},
|
||||
request::{ArchivedDeadProperty, MultiGet, PropFind, SyncCollection},
|
||||
request::{
|
||||
AddressbookQuery, ArchivedDeadProperty, CalendarQuery, Filter, MultiGet, PropFind,
|
||||
SyncCollection, Timezone, VCardPropertyWithGroup,
|
||||
},
|
||||
},
|
||||
};
|
||||
use groupware::{
|
||||
|
@ -18,6 +25,7 @@ use groupware::{
|
|||
file::{ArchivedFileNode, FileNode},
|
||||
};
|
||||
use jmap_proto::types::{collection::Collection, property::Property, value::ArchivedAclGrant};
|
||||
use propfind::PropFindItem;
|
||||
use rkyv::vec::ArchivedVec;
|
||||
use store::{
|
||||
U32_LEN,
|
||||
|
@ -42,12 +50,32 @@ pub(crate) struct DavQuery<'x> {
|
|||
pub depth_no_root: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) enum DavQueryResource<'x> {
|
||||
Uri(OwnedUri<'x>),
|
||||
Multiget {
|
||||
parent_collection: Collection,
|
||||
hrefs: Vec<String>,
|
||||
},
|
||||
Query {
|
||||
filter: DavQueryFilter,
|
||||
parent_collection: Collection,
|
||||
items: Vec<PropFindItem>,
|
||||
},
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
pub(crate) type AddressbookFilter = Vec<Filter<(), VCardPropertyWithGroup, VCardParameterName>>;
|
||||
pub(crate) type CalendarFilter =
|
||||
Vec<Filter<Vec<ICalendarComponentType>, ICalendarProperty, ICalendarParameterName>>;
|
||||
|
||||
pub(crate) enum DavQueryFilter {
|
||||
Addressbook(AddressbookFilter),
|
||||
Calendar {
|
||||
filter: CalendarFilter,
|
||||
timezone: Timezone,
|
||||
},
|
||||
}
|
||||
|
||||
pub(crate) trait ETag {
|
||||
|
@ -108,7 +136,7 @@ impl<'x> DavQuery<'x> {
|
|||
Self {
|
||||
resource: DavQueryResource::Uri(resource),
|
||||
propfind,
|
||||
base_uri: headers.base_uri().unwrap_or_default(),
|
||||
base_uri: headers.base_uri.unwrap_or_default(),
|
||||
depth: match headers.depth {
|
||||
Depth::Zero => 0,
|
||||
_ => 1,
|
||||
|
@ -130,11 +158,49 @@ impl<'x> DavQuery<'x> {
|
|||
parent_collection: collection,
|
||||
},
|
||||
propfind: multiget.properties,
|
||||
base_uri: headers.base_uri().unwrap_or_default(),
|
||||
depth: match headers.depth {
|
||||
Depth::Zero => 0,
|
||||
_ => 1,
|
||||
base_uri: headers.base_uri.unwrap_or_default(),
|
||||
ret: headers.ret,
|
||||
depth_no_root: headers.depth_no_root,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addressbook_query(
|
||||
query: AddressbookQuery,
|
||||
items: Vec<PropFindItem>,
|
||||
headers: RequestHeaders<'x>,
|
||||
) -> Self {
|
||||
Self {
|
||||
resource: DavQueryResource::Query {
|
||||
filter: DavQueryFilter::Addressbook(query.filters),
|
||||
parent_collection: Collection::AddressBook,
|
||||
items,
|
||||
},
|
||||
propfind: query.properties,
|
||||
base_uri: headers.base_uri.unwrap_or_default(),
|
||||
limit: query.limit,
|
||||
ret: headers.ret,
|
||||
depth_no_root: headers.depth_no_root,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calendar_query(
|
||||
query: CalendarQuery,
|
||||
items: Vec<PropFindItem>,
|
||||
headers: RequestHeaders<'x>,
|
||||
) -> Self {
|
||||
Self {
|
||||
resource: DavQueryResource::Query {
|
||||
filter: DavQueryFilter::Calendar {
|
||||
filter: query.filters,
|
||||
timezone: query.timezone,
|
||||
},
|
||||
parent_collection: Collection::Calendar,
|
||||
items,
|
||||
},
|
||||
propfind: query.properties,
|
||||
base_uri: headers.base_uri.unwrap_or_default(),
|
||||
ret: headers.ret,
|
||||
depth_no_root: headers.depth_no_root,
|
||||
..Default::default()
|
||||
|
@ -149,7 +215,7 @@ impl<'x> DavQuery<'x> {
|
|||
Self {
|
||||
resource: DavQueryResource::Uri(resource),
|
||||
propfind: changes.properties,
|
||||
base_uri: headers.base_uri().unwrap_or_default(),
|
||||
base_uri: headers.base_uri.unwrap_or_default(),
|
||||
from_change_id: changes
|
||||
.sync_token
|
||||
.as_deref()
|
||||
|
@ -161,6 +227,7 @@ impl<'x> DavQuery<'x> {
|
|||
limit: changes.limit,
|
||||
ret: headers.ret,
|
||||
depth_no_root: headers.depth_no_root,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,15 +240,6 @@ impl<'x> DavQuery<'x> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for DavQueryResource<'_> {
|
||||
fn default() -> Self {
|
||||
Self::Multiget {
|
||||
parent_collection: Collection::None,
|
||||
hrefs: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum ArchivedResource<'x> {
|
||||
Calendar(Archive<&'x ArchivedCalendar>),
|
||||
CalendarEvent(Archive<&'x ArchivedCalendarEvent>),
|
||||
|
|
|
@ -44,14 +44,14 @@ use trc::AddContext;
|
|||
|
||||
use crate::{
|
||||
DavError, DavErrorCondition,
|
||||
card::{CARD_ALL_PROPS, CARD_CONTAINER_PROPS, CARD_ITEM_PROPS},
|
||||
card::{CARD_ALL_PROPS, CARD_CONTAINER_PROPS, CARD_ITEM_PROPS, query::vcard_query},
|
||||
common::{DavQueryResource, uri::DavUriResource},
|
||||
file::{FILE_ALL_PROPS, FILE_CONTAINER_PROPS, FILE_ITEM_PROPS},
|
||||
principal::{CurrentUserPrincipal, propfind::PrincipalPropFind},
|
||||
};
|
||||
|
||||
use super::{
|
||||
ArchivedResource, DavCollection, DavQuery, ETag,
|
||||
ArchivedResource, DavCollection, DavQuery, DavQueryFilter, ETag,
|
||||
acl::{DavAclHandler, Privileges},
|
||||
lock::{LockData, build_lock_key},
|
||||
uri::{UriResource, Urn},
|
||||
|
@ -253,15 +253,16 @@ impl PropFindRequestHandler for Server {
|
|||
async fn handle_dav_query(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
query: DavQuery<'_>,
|
||||
mut query: DavQuery<'_>,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
let mut response = MultiStatus::new(Vec::with_capacity(16));
|
||||
let mut data = PropFindData::new();
|
||||
let collection_container;
|
||||
let collection_children;
|
||||
let mut paths;
|
||||
let mut query_filter = None;
|
||||
|
||||
match &query.resource {
|
||||
match std::mem::take(&mut query.resource) {
|
||||
DavQueryResource::Uri(resource) => {
|
||||
let account_id = resource.account_id;
|
||||
collection_container = resource.collection;
|
||||
|
@ -306,13 +307,9 @@ impl PropFindRequestHandler for Server {
|
|||
sync_token.clone().into();
|
||||
sync_token
|
||||
} else {
|
||||
let id = self
|
||||
.store()
|
||||
.get_last_change_id(account_id, collection_children)
|
||||
data.sync_token(self, account_id, collection_children)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.unwrap_or_default();
|
||||
Urn::Sync(id).to_string()
|
||||
};
|
||||
response.set_sync_token(sync_token);
|
||||
|
||||
|
@ -338,7 +335,7 @@ impl PropFindRequestHandler for Server {
|
|||
.as_ref()
|
||||
.is_none_or(|d| d.contains(item.document_id))
|
||||
})
|
||||
.map(|item| PropFindItem::new(&query, account_id, item))
|
||||
.map(|item| PropFindItem::new(query.base_uri, account_id, item))
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
if !query.depth_no_root || query.from_change_id.is_none() {
|
||||
|
@ -363,7 +360,7 @@ impl PropFindRequestHandler for Server {
|
|||
.as_ref()
|
||||
.is_none_or(|d| d.contains(item.document_id))
|
||||
})
|
||||
.map(|item| PropFindItem::new(&query, account_id, item))
|
||||
.map(|item| PropFindItem::new(query.base_uri, account_id, item))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
|
@ -386,13 +383,13 @@ impl PropFindRequestHandler for Server {
|
|||
u32,
|
||||
(Arc<DavResources>, Arc<Option<RoaringBitmap>>),
|
||||
> = AHashMap::with_capacity(3);
|
||||
collection_container = *parent_collection;
|
||||
collection_container = parent_collection;
|
||||
collection_children = collection_container.child_collection().unwrap();
|
||||
response.set_namespace(collection_container.namespace());
|
||||
|
||||
for item in hrefs {
|
||||
let resource = match self
|
||||
.validate_uri(access_token, item)
|
||||
.validate_uri(access_token, &item)
|
||||
.await
|
||||
.and_then(|r| r.into_owned_uri())
|
||||
{
|
||||
|
@ -443,7 +440,7 @@ impl PropFindRequestHandler for Server {
|
|||
.as_ref()
|
||||
.is_none_or(|docs| docs.contains(resource.document_id))
|
||||
{
|
||||
paths.push(PropFindItem::new(&query, account_id, resource));
|
||||
paths.push(PropFindItem::new(query.base_uri, account_id, resource));
|
||||
} else {
|
||||
response.add_response(
|
||||
Response::new_status([item], StatusCode::FORBIDDEN)
|
||||
|
@ -465,6 +462,18 @@ impl PropFindRequestHandler for Server {
|
|||
}
|
||||
}
|
||||
}
|
||||
DavQueryResource::Query {
|
||||
filter,
|
||||
parent_collection,
|
||||
items,
|
||||
} => {
|
||||
paths = items;
|
||||
query_filter = Some(filter);
|
||||
collection_container = parent_collection;
|
||||
collection_children = collection_container.child_collection().unwrap();
|
||||
response.set_namespace(collection_container.namespace());
|
||||
}
|
||||
DavQueryResource::None => unreachable!(),
|
||||
}
|
||||
|
||||
if query.depth == usize::MAX && paths.len() > self.core.dav.max_match_results {
|
||||
|
@ -554,9 +563,27 @@ impl PropFindRequestHandler for Server {
|
|||
};
|
||||
let archive = ArchivedResource::from_archive(&archive_, collection)
|
||||
.caused_by(trc::location!())?;
|
||||
let dead_properties = archive.dead_properties();
|
||||
|
||||
// Filter
|
||||
if let Some(query_filter) = &query_filter {
|
||||
match (query_filter, &archive) {
|
||||
(DavQueryFilter::Addressbook(filters), ArchivedResource::ContactCard(card)) => {
|
||||
if !vcard_query(&card.inner.card, filters) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
(
|
||||
DavQueryFilter::Calendar { filter, timezone },
|
||||
ArchivedResource::CalendarEvent(event),
|
||||
) => {
|
||||
todo!()
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
// Fill properties
|
||||
let dead_properties = archive.dead_properties();
|
||||
let mut fields = Vec::with_capacity(properties.len());
|
||||
let mut fields_not_found = Vec::new();
|
||||
for property in &properties {
|
||||
|
@ -958,9 +985,9 @@ impl PropFindRequestHandler for Server {
|
|||
}
|
||||
|
||||
impl PropFindItem {
|
||||
pub fn new(query: &DavQuery<'_>, account_id: u32, resource: &DavResource) -> Self {
|
||||
pub fn new(base_uri: &str, account_id: u32, resource: &DavResource) -> Self {
|
||||
Self {
|
||||
name: query.format_to_base_uri(&resource.name),
|
||||
name: format!("{}{}", base_uri, resource.name),
|
||||
account_id,
|
||||
document_id: resource.document_id,
|
||||
is_container: resource.is_container,
|
||||
|
|
|
@ -52,7 +52,7 @@ impl PrincipalMatching for Server {
|
|||
access_token,
|
||||
DavQuery {
|
||||
resource: DavQueryResource::Uri(resource),
|
||||
base_uri: headers.base_uri().unwrap_or_default(),
|
||||
base_uri: headers.base_uri.unwrap_or_default(),
|
||||
propfind: PropFind::Prop(request.properties),
|
||||
depth: usize::MAX,
|
||||
ret: headers.ret,
|
||||
|
|
Loading…
Add table
Reference in a new issue