preliminary iCal/iPhone support introduced
This commit is contained in:
parent
911cd48efe
commit
e05e94a129
@ -29,7 +29,6 @@ should have been included in this package.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import posixpath
|
|
||||||
import pprint
|
import pprint
|
||||||
import base64
|
import base64
|
||||||
import socket
|
import socket
|
||||||
@ -151,28 +150,19 @@ class Application(object):
|
|||||||
else:
|
else:
|
||||||
content = None
|
content = None
|
||||||
|
|
||||||
# Find calendar
|
# Find calendar(s)
|
||||||
attributes = posixpath.normpath(
|
items = ical.Calendar.from_path(environ["PATH_INFO"],
|
||||||
environ["PATH_INFO"].strip("/")).split("/")
|
environ.get("HTTP_DEPTH", "0"))
|
||||||
if attributes:
|
|
||||||
if attributes[-1].endswith(".ics"):
|
|
||||||
attributes.pop()
|
|
||||||
path = "/".join(attributes[:min(len(attributes), 2)])
|
|
||||||
calendar = ical.Calendar(path)
|
|
||||||
else:
|
|
||||||
calendar = None
|
|
||||||
|
|
||||||
# Get function corresponding to method
|
# Get function corresponding to method
|
||||||
function = getattr(self, environ["REQUEST_METHOD"].lower())
|
function = getattr(self, environ["REQUEST_METHOD"].lower())
|
||||||
|
|
||||||
# Check rights
|
# Check rights
|
||||||
if not calendar or not self.acl:
|
if not items or not self.acl:
|
||||||
# No calendar or no acl, don't check rights
|
# No calendar or no acl, don't check rights
|
||||||
status, headers, answer = function(environ, calendar, content)
|
status, headers, answer = function(environ, items, content)
|
||||||
else:
|
else:
|
||||||
# Ask authentication backend to check rights
|
# Ask authentication backend to check rights
|
||||||
log.LOGGER.info(
|
|
||||||
"Checking rights for calendar owned by %s" % calendar.owner)
|
|
||||||
authorization = environ.get("HTTP_AUTHORIZATION", None)
|
authorization = environ.get("HTTP_AUTHORIZATION", None)
|
||||||
|
|
||||||
if authorization:
|
if authorization:
|
||||||
@ -182,11 +172,27 @@ class Application(object):
|
|||||||
else:
|
else:
|
||||||
user = password = None
|
user = password = None
|
||||||
|
|
||||||
if self.acl.has_right(calendar.owner, user, password):
|
last_allowed = False
|
||||||
log.LOGGER.info("%s allowed" % (user or "anonymous user"))
|
calendars = []
|
||||||
status, headers, answer = function(environ, calendar, content)
|
for calendar in items:
|
||||||
|
if not isinstance(calendar, ical.Calendar):
|
||||||
|
if last_allowed:
|
||||||
|
calendars.append(calendar)
|
||||||
|
continue
|
||||||
|
log.LOGGER.info(
|
||||||
|
"Checking rights for calendar owned by %s" % calendar.owner)
|
||||||
|
|
||||||
|
if self.acl.has_right(calendar.owner, user, password):
|
||||||
|
log.LOGGER.info("%s allowed" % (user or "anonymous user"))
|
||||||
|
calendars.append(calendar)
|
||||||
|
last_allowed = True
|
||||||
|
else:
|
||||||
|
log.LOGGER.info("%s refused" % (user or "anonymous user"))
|
||||||
|
last_allowed = False
|
||||||
|
|
||||||
|
if calendars:
|
||||||
|
status, headers, answer = function(environ, calendars, content)
|
||||||
else:
|
else:
|
||||||
log.LOGGER.info("%s refused" % (user or "anonymous user"))
|
|
||||||
status = client.UNAUTHORIZED
|
status = client.UNAUTHORIZED
|
||||||
headers = {
|
headers = {
|
||||||
"WWW-Authenticate":
|
"WWW-Authenticate":
|
||||||
@ -209,8 +215,9 @@ class Application(object):
|
|||||||
# All these functions must have the same parameters, some are useless
|
# All these functions must have the same parameters, some are useless
|
||||||
# pylint: disable=W0612,W0613,R0201
|
# pylint: disable=W0612,W0613,R0201
|
||||||
|
|
||||||
def get(self, environ, calendar, content):
|
def get(self, environ, calendars, content):
|
||||||
"""Manage GET request."""
|
"""Manage GET request."""
|
||||||
|
calendar = calendars[0]
|
||||||
item_name = xmlutils.name_from_path(environ["PATH_INFO"], calendar)
|
item_name = xmlutils.name_from_path(environ["PATH_INFO"], calendar)
|
||||||
if item_name:
|
if item_name:
|
||||||
# Get calendar item
|
# Get calendar item
|
||||||
@ -235,13 +242,14 @@ class Application(object):
|
|||||||
answer = answer_text.encode(self.encoding)
|
answer = answer_text.encode(self.encoding)
|
||||||
return client.OK, headers, answer
|
return client.OK, headers, answer
|
||||||
|
|
||||||
def head(self, environ, calendar, content):
|
def head(self, environ, calendars, content):
|
||||||
"""Manage HEAD request."""
|
"""Manage HEAD request."""
|
||||||
status, headers, answer = self.get(environ, calendar, content)
|
status, headers, answer = self.get(environ, calendars, content)
|
||||||
return status, headers, None
|
return status, headers, None
|
||||||
|
|
||||||
def delete(self, environ, calendar, content):
|
def delete(self, environ, calendars, content):
|
||||||
"""Manage DELETE request."""
|
"""Manage DELETE request."""
|
||||||
|
calendar = calendars[0]
|
||||||
item = calendar.get_item(
|
item = calendar.get_item(
|
||||||
xmlutils.name_from_path(environ["PATH_INFO"], calendar))
|
xmlutils.name_from_path(environ["PATH_INFO"], calendar))
|
||||||
if item and environ.get("HTTP_IF_MATCH", item.etag) == item.etag:
|
if item and environ.get("HTTP_IF_MATCH", item.etag) == item.etag:
|
||||||
@ -254,8 +262,9 @@ class Application(object):
|
|||||||
status = client.PRECONDITION_FAILED
|
status = client.PRECONDITION_FAILED
|
||||||
return status, {}, answer
|
return status, {}, answer
|
||||||
|
|
||||||
def mkcalendar(self, environ, calendar, content):
|
def mkcalendar(self, environ, calendars, content):
|
||||||
"""Manage MKCALENDAR request."""
|
"""Manage MKCALENDAR request."""
|
||||||
|
calendar = calendars[0]
|
||||||
props = xmlutils.props_from_request(content)
|
props = xmlutils.props_from_request(content)
|
||||||
tz = props.get('C:calendar-timezone')
|
tz = props.get('C:calendar-timezone')
|
||||||
if tz:
|
if tz:
|
||||||
@ -267,7 +276,7 @@ class Application(object):
|
|||||||
calendar.write()
|
calendar.write()
|
||||||
return client.CREATED, {}, None
|
return client.CREATED, {}, None
|
||||||
|
|
||||||
def options(self, environ, calendar, content):
|
def options(self, environ, calendars, content):
|
||||||
"""Manage OPTIONS request."""
|
"""Manage OPTIONS request."""
|
||||||
headers = {
|
headers = {
|
||||||
"Allow": "DELETE, HEAD, GET, MKCALENDAR, " \
|
"Allow": "DELETE, HEAD, GET, MKCALENDAR, " \
|
||||||
@ -275,26 +284,27 @@ class Application(object):
|
|||||||
"DAV": "1, calendar-access"}
|
"DAV": "1, calendar-access"}
|
||||||
return client.OK, headers, None
|
return client.OK, headers, None
|
||||||
|
|
||||||
def propfind(self, environ, calendar, content):
|
def propfind(self, environ, calendars, content):
|
||||||
"""Manage PROPFIND request."""
|
"""Manage PROPFIND request."""
|
||||||
headers = {
|
headers = {
|
||||||
"DAV": "1, calendar-access",
|
"DAV": "1, calendar-access",
|
||||||
"Content-Type": "text/xml"}
|
"Content-Type": "text/xml"}
|
||||||
answer = xmlutils.propfind(
|
answer = xmlutils.propfind(
|
||||||
environ["PATH_INFO"], content, calendar,
|
environ["PATH_INFO"], content, calendars)
|
||||||
environ.get("HTTP_DEPTH", "infinity"))
|
|
||||||
return client.MULTI_STATUS, headers, answer
|
return client.MULTI_STATUS, headers, answer
|
||||||
|
|
||||||
def proppatch(self, environ, calendar, content):
|
def proppatch(self, environ, calendars, content):
|
||||||
"""Manage PROPPATCH request."""
|
"""Manage PROPPATCH request."""
|
||||||
|
calendar = calendars[0]
|
||||||
answer = xmlutils.proppatch(environ["PATH_INFO"], content, calendar)
|
answer = xmlutils.proppatch(environ["PATH_INFO"], content, calendar)
|
||||||
headers = {
|
headers = {
|
||||||
"DAV": "1, calendar-access",
|
"DAV": "1, calendar-access",
|
||||||
"Content-Type": "text/xml"}
|
"Content-Type": "text/xml"}
|
||||||
return client.MULTI_STATUS, headers, answer
|
return client.MULTI_STATUS, headers, answer
|
||||||
|
|
||||||
def put(self, environ, calendar, content):
|
def put(self, environ, calendars, content):
|
||||||
"""Manage PUT request."""
|
"""Manage PUT request."""
|
||||||
|
calendar = calendars[0]
|
||||||
headers = {}
|
headers = {}
|
||||||
item_name = xmlutils.name_from_path(environ["PATH_INFO"], calendar)
|
item_name = xmlutils.name_from_path(environ["PATH_INFO"], calendar)
|
||||||
item = calendar.get_item(item_name)
|
item = calendar.get_item(item_name)
|
||||||
@ -312,8 +322,10 @@ class Application(object):
|
|||||||
status = client.PRECONDITION_FAILED
|
status = client.PRECONDITION_FAILED
|
||||||
return status, headers, None
|
return status, headers, None
|
||||||
|
|
||||||
def report(self, environ, calendar, content):
|
def report(self, environ, calendars, content):
|
||||||
"""Manage REPORT request."""
|
"""Manage REPORT request."""
|
||||||
|
# TODO: support multiple calendars here
|
||||||
|
calendar = calendars[0]
|
||||||
headers = {'Content-Type': 'text/xml'}
|
headers = {'Content-Type': 'text/xml'}
|
||||||
answer = xmlutils.report(environ["PATH_INFO"], content, calendar)
|
answer = xmlutils.report(environ["PATH_INFO"], content, calendar)
|
||||||
return client.MULTI_STATUS, headers, answer
|
return client.MULTI_STATUS, headers, answer
|
||||||
|
@ -29,6 +29,7 @@ import codecs
|
|||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import posixpath
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from radicale import config
|
from radicale import config
|
||||||
@ -155,13 +156,60 @@ class Calendar(object):
|
|||||||
"""Internal calendar class."""
|
"""Internal calendar class."""
|
||||||
tag = "VCALENDAR"
|
tag = "VCALENDAR"
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path, principal=False):
|
||||||
"""Initialize the calendar with ``cal`` and ``user`` parameters."""
|
"""Initialize the calendar with ``cal`` and ``user`` parameters."""
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
split_path = path.split("/")
|
split_path = path.split("/")
|
||||||
self.owner = split_path[0] if len(split_path) > 1 else None
|
self.owner = split_path[0] if len(split_path) > 1 else None
|
||||||
self.path = os.path.join(FOLDER, path.replace("/", os.path.sep))
|
self.path = os.path.join(FOLDER, path.replace("/", os.sep))
|
||||||
self.local_path = path
|
self.local_path = path
|
||||||
|
self.is_principal = principal
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_path(cls, path, depth="infinite", include_container=True):
|
||||||
|
"""Return a list of calendars and/or sub-items under the given ``path``
|
||||||
|
relative to the storage folder. If ``depth`` is "0", only the actual
|
||||||
|
object under `path` is returned. Otherwise, also sub-items are appended
|
||||||
|
to the result. If `include_container` is True (the default), the
|
||||||
|
containing object is included in the result.
|
||||||
|
|
||||||
|
"""
|
||||||
|
attributes = posixpath.normpath(path.strip("/")).split("/")
|
||||||
|
if not attributes:
|
||||||
|
return None
|
||||||
|
if attributes[-1].endswith(".ics"):
|
||||||
|
attributes.pop()
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
path = "/".join(attributes[:min(len(attributes), 2)])
|
||||||
|
path = path.replace("/", os.sep)
|
||||||
|
abs_path = os.path.join(FOLDER, path)
|
||||||
|
if os.path.isdir(abs_path):
|
||||||
|
if depth == "0":
|
||||||
|
result.append(cls(path, principal=True))
|
||||||
|
else:
|
||||||
|
if include_container:
|
||||||
|
result.append(cls(path, principal=True))
|
||||||
|
for f in os.walk(abs_path).next()[2]:
|
||||||
|
f_path = os.path.join(path, f)
|
||||||
|
if cls.is_vcalendar(os.path.join(abs_path, f)):
|
||||||
|
result.append(cls(f_path))
|
||||||
|
else:
|
||||||
|
calendar = cls(path)
|
||||||
|
if depth == "0":
|
||||||
|
result.append(calendar)
|
||||||
|
else:
|
||||||
|
if include_container:
|
||||||
|
result.append(calendar)
|
||||||
|
result.extend(calendar.components)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_vcalendar(path):
|
||||||
|
"""Return `True` if there is a VCALENDAR file under `path`."""
|
||||||
|
with open(path) as f:
|
||||||
|
return 'BEGIN:VCALENDAR' == f.read(15)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(text, item_types, name=None):
|
def _parse(text, item_types, name=None):
|
||||||
@ -340,3 +388,7 @@ class Calendar(object):
|
|||||||
# on exit
|
# on exit
|
||||||
with open(props_path, 'w') as prop_file:
|
with open(props_path, 'w') as prop_file:
|
||||||
json.dump(properties, prop_file)
|
json.dump(properties, prop_file)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self):
|
||||||
|
return '/{}/'.format(self.local_path).replace('//', '/')
|
||||||
|
@ -42,7 +42,8 @@ NAMESPACES = {
|
|||||||
"C": "urn:ietf:params:xml:ns:caldav",
|
"C": "urn:ietf:params:xml:ns:caldav",
|
||||||
"D": "DAV:",
|
"D": "DAV:",
|
||||||
"CS": "http://calendarserver.org/ns/",
|
"CS": "http://calendarserver.org/ns/",
|
||||||
"ICAL": "http://apple.com/ns/ical/"}
|
"ICAL": "http://apple.com/ns/ical/",
|
||||||
|
"ME": "http://me.com/_namespace/"}
|
||||||
|
|
||||||
|
|
||||||
NAMESPACES_REV = {}
|
NAMESPACES_REV = {}
|
||||||
@ -104,10 +105,8 @@ def _tag_from_clark(name):
|
|||||||
args = {
|
args = {
|
||||||
'ns': NAMESPACES_REV[match.group('namespace')],
|
'ns': NAMESPACES_REV[match.group('namespace')],
|
||||||
'tag': match.group('tag')}
|
'tag': match.group('tag')}
|
||||||
tag_name = '%(ns)s:%(tag)s' % args
|
return '%(ns)s:%(tag)s' % args
|
||||||
else:
|
return name
|
||||||
tag_name = prop.tag
|
|
||||||
return tag_name
|
|
||||||
|
|
||||||
|
|
||||||
def _response(code):
|
def _response(code):
|
||||||
@ -168,7 +167,7 @@ def delete(path, calendar):
|
|||||||
return _pretty_xml(multistatus)
|
return _pretty_xml(multistatus)
|
||||||
|
|
||||||
|
|
||||||
def propfind(path, xml_request, calendar, depth):
|
def propfind(path, xml_request, calendars):
|
||||||
"""Read and answer PROPFIND requests.
|
"""Read and answer PROPFIND requests.
|
||||||
|
|
||||||
Read rfc4918-9.1 for info.
|
Read rfc4918-9.1 for info.
|
||||||
@ -183,90 +182,115 @@ def propfind(path, xml_request, calendar, depth):
|
|||||||
# Writing answer
|
# Writing answer
|
||||||
multistatus = ET.Element(_tag("D", "multistatus"))
|
multistatus = ET.Element(_tag("D", "multistatus"))
|
||||||
|
|
||||||
if calendar:
|
for calendar in calendars:
|
||||||
if depth == "0":
|
response = _propfind_response(path, calendar, props)
|
||||||
items = [calendar]
|
|
||||||
else:
|
|
||||||
# Depth is 1, infinity or not specified
|
|
||||||
# We limit ourselves to depth == 1
|
|
||||||
items = [calendar] + calendar.components
|
|
||||||
else:
|
|
||||||
items = []
|
|
||||||
|
|
||||||
for item in items:
|
|
||||||
is_calendar = isinstance(item, ical.Calendar)
|
|
||||||
|
|
||||||
response = ET.Element(_tag("D", "response"))
|
|
||||||
multistatus.append(response)
|
multistatus.append(response)
|
||||||
|
|
||||||
href = ET.Element(_tag("D", "href"))
|
return _pretty_xml(multistatus)
|
||||||
href.text = path if is_calendar else path + item.name
|
|
||||||
response.append(href)
|
|
||||||
|
|
||||||
propstat = ET.Element(_tag("D", "propstat"))
|
|
||||||
response.append(propstat)
|
|
||||||
|
|
||||||
prop = ET.Element(_tag("D", "prop"))
|
def _propfind_response(path, item, props):
|
||||||
propstat.append(prop)
|
is_calendar = isinstance(item, ical.Calendar)
|
||||||
|
if is_calendar:
|
||||||
|
with item.props as cal_props:
|
||||||
|
calendar_props = cal_props
|
||||||
|
|
||||||
for tag in props:
|
response = ET.Element(_tag("D", "response"))
|
||||||
element = ET.Element(tag)
|
|
||||||
if tag == _tag("D", "resourcetype") and is_calendar:
|
href = ET.Element(_tag("D", "href"))
|
||||||
tag = ET.Element(_tag("C", "calendar"))
|
href.text = item.url if is_calendar else path + item.name
|
||||||
element.append(tag)
|
response.append(href)
|
||||||
|
|
||||||
|
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
|
||||||
|
if tag == _tag("D", "owner"):
|
||||||
|
if item.owner:
|
||||||
|
element.text = item.owner
|
||||||
|
elif tag == _tag("D", "getcontenttype"):
|
||||||
|
element.text = "text/calendar"
|
||||||
|
elif tag == _tag("D", "getetag"):
|
||||||
|
element.text = item.etag
|
||||||
|
elif tag == _tag("D", "principal-URL"):
|
||||||
|
# TODO: use a real principal URL, read rfc3744-4.2 for info
|
||||||
|
tag = ET.Element(_tag("D", "href"))
|
||||||
|
if item.owner:
|
||||||
|
tag.text = "/{}/".format(item.owner).replace("//", "/")
|
||||||
|
else:
|
||||||
|
tag.text = path
|
||||||
|
element.append(tag)
|
||||||
|
elif tag in (
|
||||||
|
_tag("D", "principal-collection-set"),
|
||||||
|
_tag("C", "calendar-user-address-set"),
|
||||||
|
_tag("C", "calendar-home-set")):
|
||||||
|
tag = ET.Element(_tag("D", "href"))
|
||||||
|
tag.text = path
|
||||||
|
element.append(tag)
|
||||||
|
elif tag == _tag("C", "supported-calendar-component-set"):
|
||||||
|
# This is not a Todo
|
||||||
|
# pylint: disable=W0511
|
||||||
|
for component in ("VTODO", "VEVENT", "VJOURNAL"):
|
||||||
|
comp = ET.Element(_tag("C", "comp"))
|
||||||
|
comp.set("name", component)
|
||||||
|
element.append(comp)
|
||||||
|
# pylint: enable=W0511
|
||||||
|
elif tag == _tag("D", "current-user-privilege-set"):
|
||||||
|
privilege = ET.Element(_tag("D", "privilege"))
|
||||||
|
privilege.append(ET.Element(_tag("D", "all")))
|
||||||
|
element.append(privilege)
|
||||||
|
elif tag == _tag("D", "supported-report-set"):
|
||||||
|
for report_name in (
|
||||||
|
"principal-property-search", "sync-collection"
|
||||||
|
"expand-property", "principal-search-property-set"):
|
||||||
|
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)
|
||||||
|
elif is_calendar:
|
||||||
|
if tag == _tag("D", "resourcetype"):
|
||||||
|
if is_calendar and not item.is_principal:
|
||||||
|
tag = ET.Element(_tag("C", "calendar"))
|
||||||
|
element.append(tag)
|
||||||
tag = ET.Element(_tag("D", "collection"))
|
tag = ET.Element(_tag("D", "collection"))
|
||||||
element.append(tag)
|
element.append(tag)
|
||||||
elif tag == _tag("D", "owner"):
|
elif tag == _tag("CS", "getctag"):
|
||||||
if calendar.owner:
|
|
||||||
element.text = calendar.owner
|
|
||||||
elif tag == _tag("D", "getcontenttype"):
|
|
||||||
element.text = "text/calendar"
|
|
||||||
elif tag == _tag("CS", "getctag") and is_calendar:
|
|
||||||
element.text = item.etag
|
element.text = item.etag
|
||||||
elif tag == _tag("D", "getetag"):
|
else:
|
||||||
element.text = item.etag
|
human_tag = _tag_from_clark(tag)
|
||||||
elif tag == _tag("D", "displayname") and is_calendar:
|
if human_tag in calendar_props:
|
||||||
element.text = calendar.name
|
element.text = calendar_props[human_tag]
|
||||||
elif tag == _tag("D", "principal-URL"):
|
else:
|
||||||
# TODO: use a real principal URL, read rfc3744-4.2 for info
|
is404 = True
|
||||||
tag = ET.Element(_tag("D", "href"))
|
else:
|
||||||
tag.text = path
|
is404 = True
|
||||||
element.append(tag)
|
|
||||||
elif tag in (
|
|
||||||
_tag("D", "principal-collection-set"),
|
|
||||||
_tag("C", "calendar-user-address-set"),
|
|
||||||
_tag("C", "calendar-home-set")):
|
|
||||||
tag = ET.Element(_tag("D", "href"))
|
|
||||||
tag.text = path
|
|
||||||
element.append(tag)
|
|
||||||
elif tag == _tag("C", "supported-calendar-component-set"):
|
|
||||||
# This is not a Todo
|
|
||||||
# pylint: disable=W0511
|
|
||||||
for component in ("VTODO", "VEVENT", "VJOURNAL"):
|
|
||||||
comp = ET.Element(_tag("C", "comp"))
|
|
||||||
comp.set("name", component)
|
|
||||||
element.append(comp)
|
|
||||||
# pylint: enable=W0511
|
|
||||||
elif tag == _tag("D", "current-user-privilege-set"):
|
|
||||||
privilege = ET.Element(_tag("D", "privilege"))
|
|
||||||
privilege.append(ET.Element(_tag("D", "all")))
|
|
||||||
element.append(privilege)
|
|
||||||
elif tag == _tag("D", "supported-report-set"):
|
|
||||||
for report_name in (
|
|
||||||
"principal-property-search", "sync-collection"
|
|
||||||
"expand-property", "principal-search-property-set"):
|
|
||||||
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)
|
|
||||||
prop.append(element)
|
|
||||||
|
|
||||||
status = ET.Element(_tag("D", "status"))
|
if is404:
|
||||||
status.text = _response(200)
|
prop404.append(element)
|
||||||
propstat.append(status)
|
else:
|
||||||
|
prop200.append(element)
|
||||||
|
|
||||||
return _pretty_xml(multistatus)
|
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
|
||||||
|
|
||||||
|
|
||||||
def _add_propstat_to(element, tag, status_number):
|
def _add_propstat_to(element, tag, status_number):
|
||||||
|
Loading…
Reference in New Issue
Block a user