diff --git a/radicale/__init__.py b/radicale/__init__.py index 5e62f1f..55b81f7 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -256,6 +256,11 @@ class Application(object): def mkcalendar(self, environ, calendar, content): """Manage MKCALENDAR request.""" + props = xmlutils.props_from_request(content) + tz = props.get('C:calendar-timezone') + if tz: + calendar.replace('', tz) + calendar.write() return client.CREATED, {}, None def options(self, environ, calendar, content): diff --git a/radicale/ical.py b/radicale/ical.py index bb9e9bd..e929007 100644 --- a/radicale/ical.py +++ b/radicale/ical.py @@ -33,7 +33,7 @@ from radicale import config FOLDER = os.path.expanduser(config.get("storage", "folder")) - + # This function overrides the builtin ``open`` function for this module # pylint: disable=W0622 @@ -242,7 +242,7 @@ class Calendar(object): # Create folder if absent if not os.path.exists(os.path.dirname(self.path)): os.makedirs(os.path.dirname(self.path)) - + text = serialize(headers, items) return open(self.path, "w").write(text) diff --git a/radicale/xmlutils.py b/radicale/xmlutils.py index 82f9d7d..3b9e587 100644 --- a/radicale/xmlutils.py +++ b/radicale/xmlutils.py @@ -27,6 +27,12 @@ in them for XML requests (all but PUT). """ +try: + from collections import OrderedDict +except ImportError: + # Python 2.6 + OrderedDict = dict +import re import xml.etree.ElementTree as ET from radicale import client, config, ical @@ -39,7 +45,11 @@ NAMESPACES = { "ICAL": "http://apple.com/ns/ical/"} +NAMESPACES_REV = {} + + for short, url in NAMESPACES.items(): + NAMESPACES_REV[url] = short if hasattr(ET, "register_namespace"): # Register namespaces cleanly with Python 2.7+ and 3.2+ ... ET.register_namespace("" if short == "D" else short, url) @@ -48,6 +58,14 @@ for short, url in NAMESPACES.items(): ET._namespace_map[url] = short +CLARK_TAG_REGEX = re.compile(r""" + { # { + (?P[^}]*) # namespace URL + } # } + (?P.*) # short tag name + """, re.VERBOSE) + + def _pretty_xml(element, level=0): """Indent an ElementTree ``element`` and its children.""" i = "\n" + level * " " @@ -87,6 +105,31 @@ def name_from_path(path, calendar): return path_parts[-1] if (len(path_parts) - len(calendar_parts)) else None +def props_from_request(xml_request): + """Returns a list of properties as a dictionary.""" + + result = OrderedDict() + root = ET.fromstring(xml_request.encode("utf8")) + + set_element = root.find(_tag("D", "set")) + if not set_element: + set_element = root + + prop_element = set_element.find(_tag("D", "prop")) + if prop_element: + for prop in prop_element: + match = CLARK_TAG_REGEX.match(prop.tag) + if match and match.group('namespace') in NAMESPACES_REV: + args = { + 'ns': NAMESPACES_REV[match.group('namespace')], + 'tag': match.group('tag')} + tag_name = '%(ns)s:%(tag)s' % args + else: + tag_name = prop.tag + result[tag_name] = prop.text + return result + + def delete(path, calendar): """Read and answer DELETE requests.