2008-12-30 16:25:42 +00:00
|
|
|
# This file is part of Radicale Server - Calendar Server
|
2009-07-27 15:04:54 +00:00
|
|
|
# Copyright © 2008 Nicolas Kandel
|
|
|
|
# Copyright © 2008 Pascal Halter
|
2015-02-07 17:26:20 +01:00
|
|
|
# Copyright © 2008-2015 Guillaume Ayoub
|
2008-12-30 16:25:42 +00:00
|
|
|
#
|
|
|
|
# This library is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This library is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
"""
|
2009-07-27 15:04:54 +00:00
|
|
|
XML and iCal requests manager.
|
2008-12-30 16:25:42 +00:00
|
|
|
|
|
|
|
Note that all these functions need to receive unicode objects for full
|
|
|
|
iCal requests (PUT) and string objects with charset correctly defined
|
|
|
|
in them for XML requests (all but PUT).
|
2010-02-10 18:57:21 +01:00
|
|
|
|
2008-12-30 16:25:42 +00:00
|
|
|
"""
|
|
|
|
|
2016-04-11 20:11:35 +02:00
|
|
|
import posixpath
|
2011-05-24 16:12:35 +02:00
|
|
|
import re
|
2008-12-30 16:25:42 +00:00
|
|
|
import xml.etree.ElementTree as ET
|
2016-03-31 19:57:40 +02:00
|
|
|
from collections import OrderedDict
|
2016-05-30 14:53:20 +02:00
|
|
|
from datetime import datetime, timedelta, timezone
|
2016-03-31 19:57:40 +02:00
|
|
|
from urllib.parse import unquote, urlparse
|
2008-12-30 16:25:42 +00:00
|
|
|
|
2016-04-11 20:11:35 +02:00
|
|
|
import vobject
|
|
|
|
|
2016-04-22 11:37:02 +09:00
|
|
|
from . import client, storage
|
2008-12-30 16:25:42 +00:00
|
|
|
|
2010-02-10 18:57:21 +01:00
|
|
|
|
2010-04-10 16:26:22 +02:00
|
|
|
NAMESPACES = {
|
2010-02-10 18:57:21 +01:00
|
|
|
"C": "urn:ietf:params:xml:ns:caldav",
|
2011-12-31 13:31:22 +01:00
|
|
|
"CR": "urn:ietf:params:xml:ns:carddav",
|
2010-02-10 18:57:21 +01:00
|
|
|
"D": "DAV:",
|
2011-05-11 16:24:20 +02:00
|
|
|
"CS": "http://calendarserver.org/ns/",
|
2011-06-01 12:43:49 +02:00
|
|
|
"ICAL": "http://apple.com/ns/ical/",
|
|
|
|
"ME": "http://me.com/_namespace/"}
|
2008-12-30 16:25:42 +00:00
|
|
|
|
2010-02-10 18:57:21 +01:00
|
|
|
|
2011-05-24 16:12:35 +02:00
|
|
|
NAMESPACES_REV = {}
|
|
|
|
|
|
|
|
|
2011-05-10 19:16:03 +02:00
|
|
|
for short, url in NAMESPACES.items():
|
2011-05-24 16:12:35 +02:00
|
|
|
NAMESPACES_REV[url] = short
|
2016-03-31 19:57:40 +02:00
|
|
|
ET.register_namespace("" if short == "D" else short, url)
|
2011-05-09 17:02:31 +02:00
|
|
|
|
|
|
|
|
2011-05-24 16:12:35 +02:00
|
|
|
CLARK_TAG_REGEX = re.compile(r"""
|
|
|
|
{ # {
|
|
|
|
(?P<namespace>[^}]*) # namespace URL
|
|
|
|
} # }
|
|
|
|
(?P<tag>.*) # short tag name
|
|
|
|
""", re.VERBOSE)
|
|
|
|
|
|
|
|
|
2011-05-11 11:20:39 +02:00
|
|
|
def _pretty_xml(element, level=0):
|
2011-05-11 06:19:05 +02:00
|
|
|
"""Indent an ElementTree ``element`` and its children."""
|
2011-05-09 16:51:58 +02:00
|
|
|
i = "\n" + level * " "
|
2011-05-11 06:19:05 +02:00
|
|
|
if len(element):
|
|
|
|
if not element.text or not element.text.strip():
|
|
|
|
element.text = i + " "
|
|
|
|
if not element.tail or not element.tail.strip():
|
|
|
|
element.tail = i
|
|
|
|
for sub_element in element:
|
2011-05-11 11:20:39 +02:00
|
|
|
_pretty_xml(sub_element, level + 1)
|
2011-05-11 08:13:33 +02:00
|
|
|
# ``sub_element`` is always defined as len(element) > 0
|
2011-05-11 06:37:30 +02:00
|
|
|
# pylint: disable=W0631
|
2011-05-11 06:19:05 +02:00
|
|
|
if not sub_element.tail or not sub_element.tail.strip():
|
|
|
|
sub_element.tail = i
|
2011-05-11 06:37:30 +02:00
|
|
|
# pylint: enable=W0631
|
2011-05-09 16:51:58 +02:00
|
|
|
else:
|
2011-05-11 06:19:05 +02:00
|
|
|
if level and (not element.tail or not element.tail.strip()):
|
|
|
|
element.tail = i
|
2011-05-11 11:20:39 +02:00
|
|
|
if not level:
|
2016-04-22 11:37:02 +09:00
|
|
|
return '<?xml version="1.0"?>\n%s' % ET.tostring(element, "unicode")
|
2011-05-09 16:51:58 +02:00
|
|
|
|
|
|
|
|
2009-07-27 15:04:54 +00:00
|
|
|
def _tag(short_name, local):
|
|
|
|
"""Get XML Clark notation {uri(``short_name``)}``local``."""
|
2010-04-10 16:26:22 +02:00
|
|
|
return "{%s}%s" % (NAMESPACES[short_name], local)
|
2010-02-10 18:57:21 +01:00
|
|
|
|
2010-01-16 13:33:50 +01:00
|
|
|
|
2011-05-24 17:33:57 +02:00
|
|
|
def _tag_from_clark(name):
|
2011-06-02 20:15:07 +02:00
|
|
|
"""Get a human-readable variant of the XML Clark notation tag ``name``.
|
|
|
|
|
|
|
|
For a given name using the XML Clark notation, return a human-readable
|
|
|
|
variant of the tag name for known namespaces. Otherwise, return the name as
|
|
|
|
is.
|
2011-05-24 17:33:57 +02:00
|
|
|
|
|
|
|
"""
|
|
|
|
match = CLARK_TAG_REGEX.match(name)
|
2012-03-01 10:40:15 +01:00
|
|
|
if match and match.group("namespace") in NAMESPACES_REV:
|
2011-05-24 17:33:57 +02:00
|
|
|
args = {
|
2012-03-01 10:40:15 +01:00
|
|
|
"ns": NAMESPACES_REV[match.group("namespace")],
|
|
|
|
"tag": match.group("tag")}
|
|
|
|
return "%(ns)s:%(tag)s" % args
|
2011-06-01 12:43:49 +02:00
|
|
|
return name
|
2011-05-24 17:33:57 +02:00
|
|
|
|
|
|
|
|
2010-01-16 13:33:50 +01:00
|
|
|
def _response(code):
|
|
|
|
"""Return full W3C names from HTTP status codes."""
|
|
|
|
return "HTTP/1.1 %i %s" % (code, client.responses[code])
|
2008-12-30 16:25:42 +00:00
|
|
|
|
2010-02-10 18:57:21 +01:00
|
|
|
|
2016-04-22 11:37:02 +09:00
|
|
|
def _href(collection, href):
|
2013-04-30 14:02:17 +02:00
|
|
|
"""Return prefixed href."""
|
2016-04-22 11:37:02 +09:00
|
|
|
return "%s%s" % (
|
|
|
|
collection.configuration.get("server", "base_prefix"),
|
|
|
|
href.lstrip("/"))
|
2013-03-18 18:14:53 +01:00
|
|
|
|
|
|
|
|
2016-05-06 17:53:02 +02:00
|
|
|
def _comp_match(item, filter_, scope="collection"):
|
|
|
|
"""Check whether the ``item`` matches the comp ``filter_``.
|
|
|
|
|
|
|
|
If ``scope`` is ``"collection"``, the filter is applied on the
|
|
|
|
item's collection. Otherwise, it's applied on the item.
|
|
|
|
|
|
|
|
See rfc4791-9.7.1.
|
|
|
|
|
|
|
|
"""
|
|
|
|
filter_length = len(filter_)
|
|
|
|
if scope == "collection":
|
|
|
|
tag = item.collection.get_meta("tag")
|
|
|
|
else:
|
|
|
|
for component in item.components():
|
|
|
|
if component.name in ("VTODO", "VEVENT", "VJOURNAL"):
|
|
|
|
tag = component.name
|
2016-05-18 22:43:56 +02:00
|
|
|
break
|
|
|
|
else:
|
|
|
|
return False
|
2016-05-06 17:53:02 +02:00
|
|
|
if filter_length == 0:
|
|
|
|
# Point #1 of rfc4791-9.7.1
|
|
|
|
return filter_.get("name") == tag
|
|
|
|
else:
|
|
|
|
if filter_length == 1:
|
|
|
|
if filter_[0].tag == _tag("C", "is-not-defined"):
|
|
|
|
# Point #2 of rfc4791-9.7.1
|
|
|
|
return filter_.get("name") != tag
|
|
|
|
if filter_[0].tag == _tag("C", "time-range"):
|
|
|
|
# Point #3 of rfc4791-9.7.1
|
2016-05-30 14:53:20 +02:00
|
|
|
if not _time_range_match(item.item, filter_[0], tag):
|
2016-05-06 17:53:02 +02:00
|
|
|
return False
|
2016-05-30 14:53:20 +02:00
|
|
|
filter_ = filter_[1:]
|
2016-05-06 17:53:02 +02:00
|
|
|
# Point #4 of rfc4791-9.7.1
|
|
|
|
return all(
|
|
|
|
_prop_match(item, child) if child.tag == _tag("C", "prop-filter")
|
|
|
|
else _comp_match(item, child, scope="component")
|
|
|
|
for child in filter_)
|
|
|
|
|
|
|
|
|
|
|
|
def _prop_match(item, filter_):
|
|
|
|
"""Check whether the ``item`` matches the prop ``filter_``.
|
|
|
|
|
|
|
|
See rfc4791-9.7.2 and rfc6352-10.5.1.
|
|
|
|
|
|
|
|
"""
|
|
|
|
filter_length = len(filter_)
|
|
|
|
if item.collection.get_meta("tag") == "VCALENDAR":
|
|
|
|
for component in item.components():
|
|
|
|
if component.name in ("VTODO", "VEVENT", "VJOURNAL"):
|
|
|
|
vobject_item = component
|
|
|
|
else:
|
|
|
|
vobject_item = item.item
|
|
|
|
if filter_length == 0:
|
|
|
|
# Point #1 of rfc4791-9.7.2
|
|
|
|
return filter_.get("name").lower() in vobject_item.contents
|
|
|
|
else:
|
2016-05-27 14:44:59 +02:00
|
|
|
name = filter_.get("name").lower()
|
2016-05-06 17:53:02 +02:00
|
|
|
if filter_length == 1:
|
|
|
|
if filter_[0].tag == _tag("C", "is-not-defined"):
|
|
|
|
# Point #2 of rfc4791-9.7.2
|
2016-05-27 14:44:59 +02:00
|
|
|
return name not in vobject_item.contents
|
2016-05-06 17:53:02 +02:00
|
|
|
if filter_[0].tag == _tag("C", "time-range"):
|
|
|
|
# Point #3 of rfc4791-9.7.2
|
2016-05-30 14:53:20 +02:00
|
|
|
if not _time_range_match(vobject_item, filter_[0], name):
|
2016-05-06 17:53:02 +02:00
|
|
|
return False
|
2016-05-30 14:53:20 +02:00
|
|
|
filter_ = filter_[1:]
|
2016-05-06 17:53:02 +02:00
|
|
|
elif filter_[0].tag == _tag("C", "text-match"):
|
|
|
|
# Point #4 of rfc4791-9.7.2
|
2016-05-27 14:44:59 +02:00
|
|
|
if not _text_match(vobject_item, filter_[0], name):
|
2016-05-06 17:53:02 +02:00
|
|
|
return False
|
2016-05-30 14:53:20 +02:00
|
|
|
filter_ = filter_[1:]
|
2016-05-06 17:53:02 +02:00
|
|
|
return all(
|
2016-05-27 14:44:59 +02:00
|
|
|
_param_filter_match(vobject_item, param_filter, name)
|
2016-05-06 17:53:02 +02:00
|
|
|
for param_filter in filter_)
|
|
|
|
|
|
|
|
|
2016-05-30 14:53:20 +02:00
|
|
|
def _time_range_match(vobject_item, filter_, child_name):
|
2016-05-06 17:53:02 +02:00
|
|
|
"""Check whether the ``item`` matches the time-range ``filter_``.
|
|
|
|
|
|
|
|
See rfc4791-9.9.
|
|
|
|
|
|
|
|
"""
|
2016-05-30 14:53:20 +02:00
|
|
|
start = filter_.get("start")
|
|
|
|
end = filter_.get("end")
|
|
|
|
if not start and not end:
|
|
|
|
return False
|
|
|
|
if start:
|
|
|
|
start = datetime.strptime(start, "%Y%m%dT%H%M%SZ")
|
|
|
|
else:
|
2016-06-10 00:02:07 +02:00
|
|
|
start = datetime.min
|
2016-05-30 14:53:20 +02:00
|
|
|
if end:
|
|
|
|
end = datetime.strptime(end, "%Y%m%dT%H%M%SZ")
|
|
|
|
else:
|
2016-06-10 00:02:07 +02:00
|
|
|
end = datetime.max
|
2016-05-30 14:53:20 +02:00
|
|
|
start = start.replace(tzinfo=timezone.utc)
|
|
|
|
end = end.replace(tzinfo=timezone.utc)
|
|
|
|
child = getattr(vobject_item, child_name.lower())
|
|
|
|
|
|
|
|
# Comments give the lines in the tables of the specification
|
|
|
|
if child_name == "VEVENT":
|
|
|
|
# TODO: check if there's a timezone
|
|
|
|
dtstart = child.dtstart.value
|
|
|
|
if not isinstance(dtstart, datetime):
|
|
|
|
dtstart_is_datetime = False
|
|
|
|
# TODO: changing dates to datetimes may be wrong because of tz
|
|
|
|
dtstart = datetime.combine(dtstart, datetime.min.time()).replace(
|
|
|
|
tzinfo=timezone.utc)
|
|
|
|
else:
|
|
|
|
dtstart_is_datetime = True
|
|
|
|
dtend = getattr(child, "dtend", None)
|
|
|
|
duration = getattr(child, "duration", None)
|
|
|
|
if dtend is not None:
|
|
|
|
# Line 1
|
|
|
|
dtend = dtend.value
|
|
|
|
if not isinstance(dtend, datetime):
|
|
|
|
dtend = datetime.combine(dtend, datetime.min.time()).replace(
|
|
|
|
tzinfo=timezone.utc)
|
|
|
|
return start < dtend and end > dtstart
|
|
|
|
elif duration is not None:
|
|
|
|
duration = duration.value
|
|
|
|
if duration.seconds > 0:
|
|
|
|
# Line 2
|
|
|
|
return start < dtstart + duration and end > dtstart
|
|
|
|
else:
|
|
|
|
# Line 3
|
|
|
|
return start <= dtstart and end > dtstart
|
|
|
|
elif dtstart_is_datetime:
|
|
|
|
# Line 4
|
|
|
|
return start <= dtstart and end > dtstart
|
|
|
|
else:
|
|
|
|
# Line 5
|
|
|
|
return start < dtstart + timedelta(days=1) and end > dtstart
|
|
|
|
elif child_name == "VTODO":
|
|
|
|
# TODO: implement this
|
|
|
|
pass
|
|
|
|
elif child_name == "VJOURNAL":
|
|
|
|
# TODO: implement this
|
|
|
|
pass
|
2016-05-06 17:53:02 +02:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
2016-05-27 14:44:59 +02:00
|
|
|
def _text_match(vobject_item, filter_, child_name, attrib_name=None):
|
|
|
|
"""Check whether the ``item`` matches the text-match ``filter_``.
|
|
|
|
|
|
|
|
See rfc4791-9.7.5.
|
|
|
|
|
|
|
|
"""
|
|
|
|
# TODO: collations are not supported, but the default ones needed
|
|
|
|
# for DAV servers are actually pretty useless. Texts are lowered to
|
|
|
|
# be case-insensitive, almost as the "i;ascii-casemap" value.
|
|
|
|
match = next(filter_.itertext()).lower()
|
|
|
|
children = getattr(vobject_item, "%s_list" % child_name, [])
|
|
|
|
if attrib_name:
|
|
|
|
condition = any(
|
|
|
|
match in attrib.lower() for child in children
|
|
|
|
for attrib in child.params.get(attrib_name, []))
|
|
|
|
else:
|
|
|
|
condition = any(match in child.value.lower() for child in children)
|
|
|
|
if filter_.get("negate-condition") == "yes":
|
|
|
|
return not condition
|
|
|
|
else:
|
|
|
|
return condition
|
|
|
|
|
|
|
|
|
|
|
|
def _param_filter_match(vobject_item, filter_, parent_name):
|
2016-05-06 17:53:02 +02:00
|
|
|
"""Check whether the ``item`` matches the param-filter ``filter_``.
|
|
|
|
|
|
|
|
See rfc4791-9.7.3.
|
|
|
|
|
|
|
|
"""
|
2016-05-27 14:44:59 +02:00
|
|
|
name = filter_.get("name")
|
|
|
|
children = getattr(vobject_item, "%s_list" % parent_name, [])
|
|
|
|
condition = any(name in child.params for child in children)
|
|
|
|
if len(filter_):
|
|
|
|
if filter_[0].tag == _tag("C", "text-match"):
|
|
|
|
return condition and _text_match(
|
|
|
|
vobject_item, filter_[0], parent_name, name)
|
|
|
|
elif filter_[0].tag == _tag("C", "is-not-defined"):
|
|
|
|
return not condition
|
|
|
|
else:
|
|
|
|
return condition
|
2016-05-06 17:53:02 +02:00
|
|
|
|
|
|
|
|
2011-12-31 13:31:22 +01:00
|
|
|
def name_from_path(path, collection):
|
2010-04-11 22:46:57 +02:00
|
|
|
"""Return Radicale item name from ``path``."""
|
2012-01-23 16:21:30 +01:00
|
|
|
collection_parts = collection.path.strip("/").split("/")
|
2010-12-20 15:49:48 +01:00
|
|
|
path_parts = path.strip("/").split("/")
|
2011-12-31 13:31:22 +01:00
|
|
|
if (len(path_parts) - len(collection_parts)):
|
|
|
|
return path_parts[-1]
|
2010-04-11 22:46:57 +02:00
|
|
|
|
|
|
|
|
2011-05-24 17:33:57 +02:00
|
|
|
def props_from_request(root, actions=("set", "remove")):
|
2011-06-02 20:15:07 +02:00
|
|
|
"""Return a list of properties as a dictionary."""
|
2011-05-24 16:12:35 +02:00
|
|
|
result = OrderedDict()
|
2016-04-17 13:58:56 +09:00
|
|
|
if root:
|
|
|
|
if not hasattr(root, "tag"):
|
|
|
|
root = ET.fromstring(root.encode("utf8"))
|
|
|
|
else:
|
|
|
|
return result
|
2011-05-24 16:12:35 +02:00
|
|
|
|
2011-05-24 17:33:57 +02:00
|
|
|
for action in actions:
|
|
|
|
action_element = root.find(_tag("D", action))
|
|
|
|
if action_element is not None:
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
action_element = root
|
2011-05-24 16:12:35 +02:00
|
|
|
|
2011-05-24 17:33:57 +02:00
|
|
|
prop_element = action_element.find(_tag("D", "prop"))
|
|
|
|
if prop_element is not None:
|
2011-05-24 16:12:35 +02:00
|
|
|
for prop in prop_element:
|
2013-05-13 23:19:22 +02:00
|
|
|
if prop.tag == _tag("D", "resourcetype"):
|
2011-12-31 13:31:22 +01:00
|
|
|
for resource_type in prop:
|
2013-05-13 23:19:22 +02:00
|
|
|
if resource_type.tag == _tag("C", "calendar"):
|
|
|
|
result["tag"] = "VCALENDAR"
|
2011-12-31 13:31:22 +01:00
|
|
|
break
|
2013-05-13 23:19:22 +02:00
|
|
|
elif resource_type.tag == _tag("CR", "addressbook"):
|
|
|
|
result["tag"] = "VADDRESSBOOK"
|
|
|
|
break
|
|
|
|
elif prop.tag == _tag("C", "supported-calendar-component-set"):
|
2013-05-10 14:56:17 +02:00
|
|
|
result[_tag_from_clark(prop.tag)] = ",".join(
|
|
|
|
supported_comp.attrib["name"]
|
|
|
|
for supported_comp in prop
|
2013-05-13 23:19:22 +02:00
|
|
|
if supported_comp.tag == _tag("C", "comp"))
|
|
|
|
else:
|
|
|
|
result[_tag_from_clark(prop.tag)] = prop.text
|
2011-06-02 20:15:07 +02:00
|
|
|
|
2011-05-24 16:12:35 +02:00
|
|
|
return result
|
|
|
|
|
|
|
|
|
2011-12-31 13:31:22 +01:00
|
|
|
def delete(path, collection):
|
2009-07-27 15:04:54 +00:00
|
|
|
"""Read and answer DELETE requests.
|
2008-12-30 16:25:42 +00:00
|
|
|
|
2009-07-27 15:04:54 +00:00
|
|
|
Read rfc4918-9.6 for info.
|
2010-02-10 18:57:21 +01:00
|
|
|
|
2009-07-27 15:04:54 +00:00
|
|
|
"""
|
2008-12-30 16:25:42 +00:00
|
|
|
# Reading request
|
2012-01-23 16:21:30 +01:00
|
|
|
if collection.path == path.strip("/"):
|
2011-12-31 13:31:22 +01:00
|
|
|
# Delete the whole collection
|
|
|
|
collection.delete()
|
2011-11-29 17:41:08 +01:00
|
|
|
else:
|
2011-12-31 13:31:22 +01:00
|
|
|
# Remove an item from the collection
|
2016-04-11 20:11:35 +02:00
|
|
|
collection.delete(name_from_path(path, collection))
|
2008-12-30 16:25:42 +00:00
|
|
|
|
|
|
|
# Writing answer
|
2011-05-11 15:05:23 +02:00
|
|
|
multistatus = ET.Element(_tag("D", "multistatus"))
|
|
|
|
response = ET.Element(_tag("D", "response"))
|
2008-12-30 16:25:42 +00:00
|
|
|
multistatus.append(response)
|
2011-05-11 15:05:23 +02:00
|
|
|
|
|
|
|
href = ET.Element(_tag("D", "href"))
|
2016-04-22 11:37:02 +09:00
|
|
|
href.text = _href(collection, path)
|
2008-12-30 16:25:42 +00:00
|
|
|
response.append(href)
|
|
|
|
|
2011-05-11 15:05:23 +02:00
|
|
|
status = ET.Element(_tag("D", "status"))
|
2010-01-16 13:33:50 +01:00
|
|
|
status.text = _response(200)
|
2008-12-30 16:25:42 +00:00
|
|
|
response.append(status)
|
|
|
|
|
2011-05-11 11:20:39 +02:00
|
|
|
return _pretty_xml(multistatus)
|
2008-12-30 16:25:42 +00:00
|
|
|
|
2010-04-11 22:46:57 +02:00
|
|
|
|
2016-05-18 22:41:05 +02:00
|
|
|
def propfind(path, xml_request, read_collections, write_collections,
|
|
|
|
user=None):
|
2009-07-27 15:04:54 +00:00
|
|
|
"""Read and answer PROPFIND requests.
|
2008-12-30 16:25:42 +00:00
|
|
|
|
2009-07-27 15:04:54 +00:00
|
|
|
Read rfc4918-9.1 for info.
|
2012-09-15 09:08:01 +02:00
|
|
|
|
2016-04-09 15:11:47 +02:00
|
|
|
The collections parameter is a list of collections that are to be included
|
|
|
|
in the output.
|
2011-04-25 13:33:48 +02:00
|
|
|
|
2009-07-27 15:04:54 +00:00
|
|
|
"""
|
2008-12-30 16:25:42 +00:00
|
|
|
# Reading request
|
2013-02-25 20:55:15 +01:00
|
|
|
if xml_request:
|
|
|
|
root = ET.fromstring(xml_request.encode("utf8"))
|
2013-02-27 10:36:54 +01:00
|
|
|
props = [prop.tag for prop in root.find(_tag("D", "prop"))]
|
2013-02-25 20:55:15 +01:00
|
|
|
else:
|
2013-02-27 10:36:54 +01:00
|
|
|
props = [_tag("D", "getcontenttype"),
|
|
|
|
_tag("D", "resourcetype"),
|
|
|
|
_tag("D", "displayname"),
|
|
|
|
_tag("D", "owner"),
|
|
|
|
_tag("D", "getetag"),
|
2014-11-11 21:26:29 +01:00
|
|
|
_tag("ICAL", "calendar-color"),
|
2013-02-27 10:36:54 +01:00
|
|
|
_tag("CS", "getctag")]
|
2011-05-10 14:21:13 +02:00
|
|
|
|
2008-12-30 16:25:42 +00:00
|
|
|
# Writing answer
|
2011-05-11 15:05:23 +02:00
|
|
|
multistatus = ET.Element(_tag("D", "multistatus"))
|
2008-12-30 16:25:42 +00:00
|
|
|
|
2015-06-04 11:23:01 +12:00
|
|
|
collections = []
|
|
|
|
for collection in write_collections:
|
|
|
|
collections.append(collection)
|
2016-05-18 22:41:05 +02:00
|
|
|
response = _propfind_response(
|
|
|
|
path, collection, props, user, write=True)
|
2015-06-04 11:23:01 +12:00
|
|
|
multistatus.append(response)
|
|
|
|
for collection in read_collections:
|
|
|
|
if collection in collections:
|
|
|
|
continue
|
2016-05-18 22:41:05 +02:00
|
|
|
response = _propfind_response(
|
|
|
|
path, collection, props, user, write=False)
|
2012-08-15 22:36:42 +02:00
|
|
|
multistatus.append(response)
|
2010-11-30 11:57:37 +01:00
|
|
|
|
2011-06-01 12:43:49 +02:00
|
|
|
return _pretty_xml(multistatus)
|
2010-11-30 11:57:37 +01:00
|
|
|
|
2010-09-28 16:32:47 +02:00
|
|
|
|
2015-06-04 11:23:01 +12:00
|
|
|
def _propfind_response(path, item, props, user, write=False):
|
2011-06-02 20:15:07 +02:00
|
|
|
"""Build and return a PROPFIND response."""
|
2016-04-22 11:37:02 +09:00
|
|
|
# TODO: fix this
|
|
|
|
is_collection = hasattr(item, "list")
|
2011-12-31 13:31:22 +01:00
|
|
|
if is_collection:
|
2016-04-11 20:11:35 +02:00
|
|
|
is_leaf = bool(item.list())
|
2016-04-22 11:37:02 +09:00
|
|
|
collection = item
|
|
|
|
else:
|
|
|
|
collection = item.collection
|
2010-09-28 16:32:47 +02:00
|
|
|
|
2011-06-01 12:43:49 +02:00
|
|
|
response = ET.Element(_tag("D", "response"))
|
2010-09-28 16:32:47 +02:00
|
|
|
|
2011-06-01 12:43:49 +02:00
|
|
|
href = ET.Element(_tag("D", "href"))
|
2016-04-12 18:21:18 +02:00
|
|
|
if is_collection:
|
|
|
|
uri = item.path
|
|
|
|
else:
|
|
|
|
# TODO: fix this
|
|
|
|
if path.split("/")[-1] == item.href:
|
|
|
|
# Happening when depth is 0
|
|
|
|
uri = path
|
|
|
|
else:
|
|
|
|
# Happening when depth is 1
|
|
|
|
uri = "/".join((path, item.href))
|
|
|
|
|
|
|
|
# TODO: fix this
|
2016-04-22 11:37:02 +09:00
|
|
|
href.text = _href(collection, uri.replace("//", "/"))
|
2011-06-01 12:43:49 +02:00
|
|
|
response.append(href)
|
2010-09-28 16:32:47 +02:00
|
|
|
|
2011-06-01 12:43:49 +02:00
|
|
|
propstat404 = ET.Element(_tag("D", "propstat"))
|
|
|
|
propstat200 = ET.Element(_tag("D", "propstat"))
|
|
|
|
response.append(propstat200)
|
|
|
|
|
|
|
|
prop200 = ET.Element(_tag("D", "prop"))
|
|
|
|
propstat200.append(prop200)
|
|
|
|
|
|
|
|
prop404 = ET.Element(_tag("D", "prop"))
|
|
|
|
propstat404.append(prop404)
|
|
|
|
|
|
|
|
for tag in props:
|
|
|
|
element = ET.Element(tag)
|
|
|
|
is404 = False
|
2014-07-24 18:56:00 +02:00
|
|
|
if tag == _tag("D", "getetag"):
|
2016-04-20 07:49:03 +09:00
|
|
|
element.text = item.etag
|
2014-07-24 18:56:00 +02:00
|
|
|
elif tag == _tag("D", "principal-URL"):
|
2014-01-05 21:21:17 +01:00
|
|
|
tag = ET.Element(_tag("D", "href"))
|
2016-04-22 11:37:02 +09:00
|
|
|
tag.text = _href(collection, path)
|
2014-01-05 21:21:17 +01:00
|
|
|
element.append(tag)
|
2016-04-18 09:11:00 +09:00
|
|
|
elif tag == _tag("D", "getlastmodified"):
|
|
|
|
element.text = item.last_modified
|
2014-07-24 18:56:00 +02:00
|
|
|
elif tag in (_tag("D", "principal-collection-set"),
|
|
|
|
_tag("C", "calendar-user-address-set"),
|
|
|
|
_tag("CR", "addressbook-home-set"),
|
|
|
|
_tag("C", "calendar-home-set")):
|
2011-06-01 12:43:49 +02:00
|
|
|
tag = ET.Element(_tag("D", "href"))
|
2016-04-22 11:37:02 +09:00
|
|
|
tag.text = _href(collection, path)
|
2011-06-01 12:43:49 +02:00
|
|
|
element.append(tag)
|
|
|
|
elif tag == _tag("C", "supported-calendar-component-set"):
|
|
|
|
# This is not a Todo
|
|
|
|
# pylint: disable=W0511
|
2013-05-10 14:56:17 +02:00
|
|
|
human_tag = _tag_from_clark(tag)
|
2016-04-09 15:11:47 +02:00
|
|
|
if is_collection and is_leaf:
|
2016-04-11 20:11:35 +02:00
|
|
|
meta = item.get_meta(human_tag)
|
|
|
|
if meta:
|
|
|
|
components = meta.split(",")
|
2016-04-09 15:11:47 +02:00
|
|
|
else:
|
|
|
|
components = ("VTODO", "VEVENT", "VJOURNAL")
|
|
|
|
for component in components:
|
|
|
|
comp = ET.Element(_tag("C", "comp"))
|
|
|
|
comp.set("name", component)
|
|
|
|
element.append(comp)
|
2013-05-10 14:56:17 +02:00
|
|
|
else:
|
2016-04-09 15:11:47 +02:00
|
|
|
is404 = True
|
2011-06-01 12:43:49 +02:00
|
|
|
# pylint: enable=W0511
|
2014-07-24 18:56:00 +02:00
|
|
|
elif tag == _tag("D", "current-user-principal") and user:
|
|
|
|
tag = ET.Element(_tag("D", "href"))
|
2016-04-22 11:37:02 +09:00
|
|
|
tag.text = _href(collection, "/%s/" % user)
|
2014-07-24 18:56:00 +02:00
|
|
|
element.append(tag)
|
2011-06-01 12:43:49 +02:00
|
|
|
elif tag == _tag("D", "current-user-privilege-set"):
|
|
|
|
privilege = ET.Element(_tag("D", "privilege"))
|
2015-06-04 11:23:01 +12:00
|
|
|
if write:
|
2016-03-04 11:05:13 +05:00
|
|
|
privilege.append(ET.Element(_tag("D", "all")))
|
|
|
|
privilege.append(ET.Element(_tag("D", "write")))
|
|
|
|
privilege.append(ET.Element(_tag("D", "write-properties")))
|
|
|
|
privilege.append(ET.Element(_tag("D", "write-content")))
|
2012-03-19 12:35:39 +01:00
|
|
|
privilege.append(ET.Element(_tag("D", "read")))
|
2011-06-01 12:43:49 +02:00
|
|
|
element.append(privilege)
|
|
|
|
elif tag == _tag("D", "supported-report-set"):
|
|
|
|
for report_name in (
|
2013-04-09 00:42:25 +02:00
|
|
|
"principal-property-search", "sync-collection",
|
2012-08-03 14:08:11 +02:00
|
|
|
"expand-property", "principal-search-property-set"):
|
2011-06-01 12:43:49 +02:00
|
|
|
supported = ET.Element(_tag("D", "supported-report"))
|
|
|
|
report_tag = ET.Element(_tag("D", "report"))
|
|
|
|
report_tag.text = report_name
|
|
|
|
supported.append(report_tag)
|
|
|
|
element.append(supported)
|
2014-07-24 18:56:00 +02:00
|
|
|
elif is_collection:
|
|
|
|
if tag == _tag("D", "getcontenttype"):
|
2016-05-25 14:05:05 +02:00
|
|
|
item_tag = item.get_meta("tag")
|
|
|
|
if item_tag:
|
|
|
|
element.text = storage.MIMETYPES[item_tag]
|
|
|
|
else:
|
|
|
|
is404 = True
|
2014-07-24 18:56:00 +02:00
|
|
|
elif tag == _tag("D", "resourcetype"):
|
|
|
|
if item.is_principal:
|
|
|
|
tag = ET.Element(_tag("D", "principal"))
|
2011-06-01 12:43:49 +02:00
|
|
|
element.append(tag)
|
2016-04-11 20:11:35 +02:00
|
|
|
item_tag = item.get_meta("tag")
|
|
|
|
if is_leaf or item_tag:
|
2014-07-24 18:56:00 +02:00
|
|
|
# 2nd case happens when the collection is not stored yet,
|
|
|
|
# but the resource type is guessed
|
2016-04-11 20:11:35 +02:00
|
|
|
if item.get_meta("tag") == "VADDRESSBOOK":
|
|
|
|
tag = ET.Element(_tag("CR", "addressbook"))
|
|
|
|
element.append(tag)
|
|
|
|
elif item.get_meta("tag") == "VCALENDAR":
|
|
|
|
tag = ET.Element(_tag("C", "calendar"))
|
|
|
|
element.append(tag)
|
2014-07-24 18:56:00 +02:00
|
|
|
tag = ET.Element(_tag("D", "collection"))
|
|
|
|
element.append(tag)
|
2016-04-09 15:11:47 +02:00
|
|
|
elif is_leaf:
|
2016-04-11 20:11:35 +02:00
|
|
|
if tag == _tag("D", "owner") and item.owner:
|
|
|
|
element.text = "/%s/" % item.owner
|
2016-04-09 15:11:47 +02:00
|
|
|
elif tag == _tag("CS", "getctag"):
|
|
|
|
element.text = item.etag
|
|
|
|
elif tag == _tag("C", "calendar-timezone"):
|
2016-04-21 10:40:36 +05:30
|
|
|
timezones = set()
|
|
|
|
for href, _ in item.list():
|
|
|
|
event = item.get(href)
|
|
|
|
if "vtimezone" in event.contents:
|
2016-05-30 14:53:20 +02:00
|
|
|
for timezone_ in event.vtimezone_list:
|
|
|
|
timezones.add(timezone_)
|
2016-06-09 23:52:05 +02:00
|
|
|
timezone_collection = vobject.iCalendar()
|
2016-05-30 14:53:20 +02:00
|
|
|
for timezone_ in timezones:
|
2016-06-09 23:52:05 +02:00
|
|
|
timezone_collection.add(timezone_)
|
|
|
|
element.text = timezone_collection.serialize()
|
2016-04-09 15:11:47 +02:00
|
|
|
elif tag == _tag("D", "displayname"):
|
2016-04-11 20:11:35 +02:00
|
|
|
element.text = item.get_meta("D:displayname") or item.path
|
2016-04-09 15:11:47 +02:00
|
|
|
elif tag == _tag("ICAL", "calendar-color"):
|
2016-04-11 20:11:35 +02:00
|
|
|
element.text = item.get_meta("ICAL:calendar-color")
|
2014-07-24 18:56:00 +02:00
|
|
|
else:
|
2016-04-09 15:11:47 +02:00
|
|
|
human_tag = _tag_from_clark(tag)
|
2016-04-11 20:11:35 +02:00
|
|
|
meta = item.get_meta(human_tag)
|
|
|
|
if meta:
|
|
|
|
element.text = meta
|
2016-04-09 15:11:47 +02:00
|
|
|
else:
|
|
|
|
is404 = True
|
|
|
|
else:
|
|
|
|
is404 = True
|
2014-07-24 18:56:00 +02:00
|
|
|
# Not for collections
|
|
|
|
elif tag == _tag("D", "getcontenttype"):
|
2016-04-11 20:11:35 +02:00
|
|
|
name = item.name.lower()
|
|
|
|
mimetype = "text/vcard" if name == "vcard" else "text/calendar"
|
|
|
|
element.text = "%s; component=%s" % (mimetype, name)
|
2011-06-08 07:43:40 +02:00
|
|
|
elif tag == _tag("D", "resourcetype"):
|
|
|
|
# resourcetype must be returned empty for non-collection elements
|
|
|
|
pass
|
2016-04-18 09:11:00 +09:00
|
|
|
elif tag == _tag("D", "getcontentlength"):
|
2016-04-22 11:37:02 +09:00
|
|
|
encoding = collection.configuration.get("encoding", "request")
|
|
|
|
element.text = str(len(item.serialize().encode(encoding)))
|
2011-06-01 12:43:49 +02:00
|
|
|
else:
|
|
|
|
is404 = True
|
2010-09-28 16:32:47 +02:00
|
|
|
|
2011-06-01 12:43:49 +02:00
|
|
|
if is404:
|
|
|
|
prop404.append(element)
|
|
|
|
else:
|
|
|
|
prop200.append(element)
|
|
|
|
|
|
|
|
status200 = ET.Element(_tag("D", "status"))
|
|
|
|
status200.text = _response(200)
|
|
|
|
propstat200.append(status200)
|
|
|
|
|
|
|
|
status404 = ET.Element(_tag("D", "status"))
|
|
|
|
status404.text = _response(404)
|
|
|
|
propstat404.append(status404)
|
|
|
|
if len(prop404):
|
|
|
|
response.append(propstat404)
|
|
|
|
|
|
|
|
return response
|
2008-12-30 16:25:42 +00:00
|
|
|
|
2010-04-11 22:46:57 +02:00
|
|
|
|
2011-05-24 17:33:57 +02:00
|
|
|
def _add_propstat_to(element, tag, status_number):
|
2011-06-02 20:15:07 +02:00
|
|
|
"""Add a PROPSTAT response structure to an element.
|
|
|
|
|
|
|
|
The PROPSTAT answer structure is defined in rfc4918-9.1. It is added to the
|
|
|
|
given ``element``, for the following ``tag`` with the given
|
|
|
|
``status_number``.
|
|
|
|
|
|
|
|
"""
|
2011-05-24 17:33:57 +02:00
|
|
|
propstat = ET.Element(_tag("D", "propstat"))
|
|
|
|
element.append(propstat)
|
|
|
|
|
|
|
|
prop = ET.Element(_tag("D", "prop"))
|
|
|
|
propstat.append(prop)
|
|
|
|
|
2012-03-01 10:40:15 +01:00
|
|
|
if "{" in tag:
|
2011-05-24 17:33:57 +02:00
|
|
|
clark_tag = tag
|
|
|
|
else:
|
2012-03-01 10:40:15 +01:00
|
|
|
clark_tag = _tag(*tag.split(":", 1))
|
2011-05-24 17:33:57 +02:00
|
|
|
prop_tag = ET.Element(clark_tag)
|
|
|
|
prop.append(prop_tag)
|
|
|
|
|
|
|
|
status = ET.Element(_tag("D", "status"))
|
|
|
|
status.text = _response(status_number)
|
|
|
|
propstat.append(status)
|
|
|
|
|
|
|
|
|
2011-12-31 13:31:22 +01:00
|
|
|
def proppatch(path, xml_request, collection):
|
2011-04-28 18:04:34 +02:00
|
|
|
"""Read and answer PROPPATCH requests.
|
|
|
|
|
|
|
|
Read rfc4918-9.2 for info.
|
|
|
|
|
|
|
|
"""
|
|
|
|
# Reading request
|
2011-05-24 15:09:37 +02:00
|
|
|
root = ET.fromstring(xml_request.encode("utf8"))
|
2012-03-01 10:40:15 +01:00
|
|
|
props_to_set = props_from_request(root, actions=("set",))
|
|
|
|
props_to_remove = props_from_request(root, actions=("remove",))
|
2011-04-28 18:04:34 +02:00
|
|
|
|
|
|
|
# Writing answer
|
2011-05-11 15:05:23 +02:00
|
|
|
multistatus = ET.Element(_tag("D", "multistatus"))
|
2011-04-28 18:04:34 +02:00
|
|
|
|
2011-05-11 15:05:23 +02:00
|
|
|
response = ET.Element(_tag("D", "response"))
|
2011-04-28 18:04:34 +02:00
|
|
|
multistatus.append(response)
|
|
|
|
|
2011-05-11 15:05:23 +02:00
|
|
|
href = ET.Element(_tag("D", "href"))
|
2016-04-22 11:37:02 +09:00
|
|
|
href.text = _href(collection, path)
|
2011-04-28 18:04:34 +02:00
|
|
|
response.append(href)
|
|
|
|
|
2016-04-13 23:02:00 +02:00
|
|
|
for short_name, value in props_to_set.items():
|
|
|
|
collection.set_meta(short_name, value)
|
|
|
|
_add_propstat_to(response, short_name, 200)
|
|
|
|
|
|
|
|
for short_name in props_to_remove:
|
|
|
|
collection.set_meta(short_name, '')
|
|
|
|
_add_propstat_to(response, short_name, 200)
|
2011-04-28 18:04:34 +02:00
|
|
|
|
2011-05-11 11:20:39 +02:00
|
|
|
return _pretty_xml(multistatus)
|
2011-04-28 18:04:34 +02:00
|
|
|
|
|
|
|
|
2014-03-05 19:26:42 +01:00
|
|
|
def report(path, xml_request, collection):
|
2009-07-27 15:04:54 +00:00
|
|
|
"""Read and answer REPORT requests.
|
2008-12-30 16:25:42 +00:00
|
|
|
|
2009-07-27 15:04:54 +00:00
|
|
|
Read rfc3253-3.6 for info.
|
2010-02-10 18:57:21 +01:00
|
|
|
|
2009-07-27 15:04:54 +00:00
|
|
|
"""
|
2008-12-30 16:25:42 +00:00
|
|
|
# Reading request
|
2011-05-24 15:09:37 +02:00
|
|
|
root = ET.fromstring(xml_request.encode("utf8"))
|
2008-12-30 16:25:42 +00:00
|
|
|
|
2010-02-10 18:57:21 +01:00
|
|
|
prop_element = root.find(_tag("D", "prop"))
|
2015-05-01 10:31:25 +02:00
|
|
|
props = (
|
|
|
|
[prop.tag for prop in prop_element]
|
|
|
|
if prop_element is not None else [])
|
2008-12-30 16:25:42 +00:00
|
|
|
|
2014-03-05 19:26:42 +01:00
|
|
|
if collection:
|
2012-08-03 14:08:11 +02:00
|
|
|
if root.tag in (_tag("C", "calendar-multiget"),
|
|
|
|
_tag("CR", "addressbook-multiget")):
|
2011-02-12 12:05:02 +01:00
|
|
|
# Read rfc4791-7.9 for info
|
2016-04-22 11:37:02 +09:00
|
|
|
base_prefix = collection.configuration.get("server", "base_prefix")
|
2014-08-05 15:42:39 +02:00
|
|
|
hreferences = set()
|
|
|
|
for href_element in root.findall(_tag("D", "href")):
|
|
|
|
href_path = unquote(urlparse(href_element.text).path)
|
|
|
|
if href_path.startswith(base_prefix):
|
2016-04-19 10:44:02 +09:00
|
|
|
hreferences.add(href_path[len(base_prefix) - 1:])
|
2011-02-12 12:05:02 +01:00
|
|
|
else:
|
|
|
|
hreferences = (path,)
|
2016-05-06 17:53:02 +02:00
|
|
|
filters = (
|
|
|
|
root.findall(".//%s" % _tag("C", "filter")) +
|
|
|
|
root.findall(".//%s" % _tag("CR", "filter")))
|
2008-12-30 16:25:42 +00:00
|
|
|
else:
|
2016-05-06 17:53:02 +02:00
|
|
|
hreferences = filters = ()
|
2008-12-30 16:25:42 +00:00
|
|
|
|
|
|
|
# Writing answer
|
2011-05-11 15:05:23 +02:00
|
|
|
multistatus = ET.Element(_tag("D", "multistatus"))
|
2008-12-30 16:25:42 +00:00
|
|
|
|
|
|
|
for hreference in hreferences:
|
2014-03-05 19:26:42 +01:00
|
|
|
# Check if the reference is an item or a collection
|
|
|
|
name = name_from_path(hreference, collection)
|
2016-04-11 20:11:35 +02:00
|
|
|
|
2014-03-05 19:26:42 +01:00
|
|
|
if name:
|
|
|
|
# Reference is an item
|
|
|
|
path = "/".join(hreference.split("/")[:-1]) + "/"
|
2016-04-13 22:56:57 +02:00
|
|
|
item = collection.get(name)
|
|
|
|
if item is None:
|
2015-05-01 10:31:25 +02:00
|
|
|
multistatus.append(
|
|
|
|
_item_response(hreference, found_item=False))
|
2015-02-08 17:52:55 +01:00
|
|
|
continue
|
|
|
|
|
2016-04-13 22:56:57 +02:00
|
|
|
items = [item]
|
|
|
|
|
2014-03-05 19:26:42 +01:00
|
|
|
else:
|
|
|
|
# Reference is a collection
|
|
|
|
path = hreference
|
2016-04-11 20:11:35 +02:00
|
|
|
items = [collection.get(href) for href, etag in collection.list()]
|
2014-03-05 19:26:42 +01:00
|
|
|
|
2010-04-11 22:46:57 +02:00
|
|
|
for item in items:
|
2016-05-06 17:53:02 +02:00
|
|
|
if filters:
|
|
|
|
match = (
|
|
|
|
_comp_match if collection.get_meta("tag") == "VCALENDAR"
|
|
|
|
else _prop_match)
|
|
|
|
if not all(match(item, filter_[0]) for filter_ in filters):
|
|
|
|
continue
|
2012-08-08 16:37:18 +02:00
|
|
|
|
2015-02-08 17:52:55 +01:00
|
|
|
found_props = []
|
|
|
|
not_found_props = []
|
2008-12-30 16:25:42 +00:00
|
|
|
|
2011-01-07 15:25:05 +01:00
|
|
|
for tag in props:
|
|
|
|
element = ET.Element(tag)
|
|
|
|
if tag == _tag("D", "getetag"):
|
|
|
|
element.text = item.etag
|
2015-02-08 17:52:55 +01:00
|
|
|
found_props.append(element)
|
2013-05-14 13:18:12 +02:00
|
|
|
elif tag == _tag("D", "getcontenttype"):
|
2016-04-11 20:11:35 +02:00
|
|
|
name = item.name.lower()
|
|
|
|
mimetype = (
|
|
|
|
"text/vcard" if name == "vcard" else "text/calendar")
|
|
|
|
element.text = "%s; component=%s" % (mimetype, name)
|
2015-02-08 17:52:55 +01:00
|
|
|
found_props.append(element)
|
2012-08-03 14:08:11 +02:00
|
|
|
elif tag in (_tag("C", "calendar-data"),
|
|
|
|
_tag("CR", "address-data")):
|
2016-04-22 11:37:02 +09:00
|
|
|
element.text = item.serialize()
|
2015-02-08 17:52:55 +01:00
|
|
|
found_props.append(element)
|
|
|
|
else:
|
|
|
|
not_found_props.append(element)
|
|
|
|
|
2016-04-12 18:21:18 +02:00
|
|
|
# TODO: fix this
|
|
|
|
if hreference.split("/")[-1] == item.href:
|
|
|
|
# Happening when depth is 0
|
2016-04-19 21:44:00 +02:00
|
|
|
uri = hreference
|
2016-04-12 18:21:18 +02:00
|
|
|
else:
|
|
|
|
# Happening when depth is 1
|
|
|
|
uri = posixpath.join(hreference, item.href)
|
2015-05-01 10:31:25 +02:00
|
|
|
multistatus.append(_item_response(
|
2016-04-12 18:21:18 +02:00
|
|
|
uri, found_props=found_props,
|
2016-04-11 20:11:35 +02:00
|
|
|
not_found_props=not_found_props, found_item=True))
|
2015-02-08 17:52:55 +01:00
|
|
|
|
|
|
|
return _pretty_xml(multistatus)
|
|
|
|
|
2008-12-30 16:25:42 +00:00
|
|
|
|
2015-02-08 17:52:55 +01:00
|
|
|
def _item_response(href, found_props=(), not_found_props=(), found_item=True):
|
|
|
|
response = ET.Element(_tag("D", "response"))
|
|
|
|
|
|
|
|
href_tag = ET.Element(_tag("D", "href"))
|
|
|
|
href_tag.text = href
|
|
|
|
response.append(href_tag)
|
|
|
|
|
|
|
|
if found_item:
|
|
|
|
if found_props:
|
|
|
|
propstat = ET.Element(_tag("D", "propstat"))
|
2011-05-11 15:05:23 +02:00
|
|
|
status = ET.Element(_tag("D", "status"))
|
2010-01-16 13:33:50 +01:00
|
|
|
status.text = _response(200)
|
2015-02-08 17:52:55 +01:00
|
|
|
prop = ET.Element(_tag("D", "prop"))
|
|
|
|
for p in found_props:
|
|
|
|
prop.append(p)
|
|
|
|
propstat.append(prop)
|
2008-12-30 16:25:42 +00:00
|
|
|
propstat.append(status)
|
2015-02-08 17:52:55 +01:00
|
|
|
response.append(propstat)
|
2008-12-30 16:25:42 +00:00
|
|
|
|
2015-02-08 17:52:55 +01:00
|
|
|
if not_found_props:
|
|
|
|
propstat = ET.Element(_tag("D", "propstat"))
|
|
|
|
status = ET.Element(_tag("D", "status"))
|
|
|
|
status.text = _response(404)
|
2015-05-15 13:47:44 +01:00
|
|
|
prop = ET.Element(_tag("D", "prop"))
|
2015-02-08 17:52:55 +01:00
|
|
|
for p in not_found_props:
|
|
|
|
prop.append(p)
|
|
|
|
propstat.append(prop)
|
|
|
|
propstat.append(status)
|
|
|
|
response.append(propstat)
|
|
|
|
else:
|
|
|
|
status = ET.Element(_tag("D", "status"))
|
|
|
|
status.text = _response(404)
|
|
|
|
response.append(status)
|
|
|
|
|
|
|
|
return response
|