1
0
Fork 0
mirror of https://github.com/Kozea/Radicale.git synced 2025-06-26 16:45:52 +00:00
Radicale/radicale/xmlutils.py

179 lines
5.9 KiB
Python
Raw Normal View History

# This file is part of Radicale Server - Calendar Server
# Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter
2015-02-07 17:26:20 +01:00
# Copyright © 2008-2015 Guillaume Ayoub
2018-08-28 16:19:36 +02:00
# Copyright © 2017-2018 Unrud <unrud@outlook.com>
#
# 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/>.
"""
XML and iCal requests manager.
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).
"""
import copy
import re
import xml.etree.ElementTree as ET
2016-03-31 19:57:40 +02:00
from collections import OrderedDict
2016-08-05 02:14:49 +02:00
from http import client
2018-08-28 16:19:36 +02:00
from urllib.parse import quote
2016-08-13 04:51:42 +02:00
2018-08-28 16:19:50 +02:00
from radicale import pathutils
2016-08-05 02:14:49 +02:00
MIMETYPES = {
"VADDRESSBOOK": "text/vcard",
"VCALENDAR": "text/calendar"}
OBJECT_MIMETYPES = {
"VCARD": "text/vcard",
"VLIST": "text/x-vlist",
"VCALENDAR": "text/calendar"}
NAMESPACES = {
"C": "urn:ietf:params:xml:ns:caldav",
2011-12-31 13:31:22 +01:00
"CR": "urn:ietf:params:xml:ns:carddav",
"D": "DAV:",
"CS": "http://calendarserver.org/ns/",
"ICAL": "http://apple.com/ns/ical/",
"ME": "http://me.com/_namespace/",
"RADICALE": "http://radicale.org/ns/"}
NAMESPACES_REV = {}
for short, url in NAMESPACES.items():
NAMESPACES_REV[url] = short
2016-03-31 19:57:40 +02:00
ET.register_namespace("" if short == "D" else short, url)
CLARK_TAG_REGEX = re.compile(r"{(?P<namespace>[^}]*)}(?P<tag>.*)", re.VERBOSE)
HUMAN_REGEX = re.compile(r"(?P<namespace>[^:{}]*):(?P<tag>.*)", re.VERBOSE)
def pretty_xml(element, level=0):
2011-05-11 06:19:05 +02:00
"""Indent an ElementTree ``element`` and its children."""
if not level:
element = copy.deepcopy(element)
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:
pretty_xml(sub_element, level + 1)
2011-05-11 06:19:05 +02:00
if not sub_element.tail or not sub_element.tail.strip():
sub_element.tail = i
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:
return '<?xml version="1.0"?>\n%s' % ET.tostring(element, "unicode")
2018-08-28 16:19:36 +02:00
def make_tag(short_name, local):
"""Get XML Clark notation {uri(``short_name``)}``local``."""
return "{%s}%s" % (NAMESPACES[short_name], local)
2010-01-16 13:33:50 +01:00
2018-08-28 16:19:36 +02:00
def tag_from_clark(name):
"""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)
if match and match.group("namespace") in NAMESPACES_REV:
2011-05-24 17:33:57 +02:00
args = {
"ns": NAMESPACES_REV[match.group("namespace")],
"tag": match.group("tag")}
return "%(ns)s:%(tag)s" % args
return name
2011-05-24 17:33:57 +02:00
2018-08-28 16:19:36 +02:00
def tag_from_human(name):
"""Get an XML Clark notation tag from human-readable variant ``name``."""
match = HUMAN_REGEX.match(name)
if match and match.group("namespace") in NAMESPACES:
2018-08-28 16:19:36 +02:00
return make_tag(match.group("namespace"), match.group("tag"))
return name
2018-08-28 16:19:36 +02:00
def make_response(code):
2010-01-16 13:33:50 +01:00
"""Return full W3C names from HTTP status codes."""
return "HTTP/1.1 %i %s" % (code, client.responses[code])
2018-08-28 16:19:36 +02:00
def make_href(base_prefix, href):
"""Return prefixed href."""
2018-08-28 16:19:50 +02:00
assert href == pathutils.sanitize_path(href)
return quote("%s%s" % (base_prefix, href))
def webdav_error(namespace, name):
"""Generate XML error message."""
2018-08-28 16:19:36 +02:00
root = ET.Element(make_tag("D", "error"))
root.append(ET.Element(make_tag(namespace, name)))
return root
def get_content_type(item):
"""Get the content-type of an item with charset and component parameters.
"""
mimetype = OBJECT_MIMETYPES[item.name]
encoding = item.collection.configuration.get("encoding", "request")
2017-08-29 20:08:28 +02:00
tag = item.component_name
content_type = "%s;charset=%s" % (mimetype, encoding)
if tag:
content_type += ";component=%s" % tag
return content_type
def props_from_request(xml_request, actions=("set", "remove")):
"""Return a list of properties as a dictionary."""
result = OrderedDict()
if xml_request is None:
return result
2011-05-24 17:33:57 +02:00
for action in actions:
2018-08-28 16:19:36 +02:00
action_element = xml_request.find(make_tag("D", action))
2011-05-24 17:33:57 +02:00
if action_element is not None:
break
else:
action_element = xml_request
2018-08-28 16:19:36 +02:00
prop_element = action_element.find(make_tag("D", "prop"))
2011-05-24 17:33:57 +02:00
if prop_element is not None:
for prop in prop_element:
2018-08-28 16:19:36 +02:00
if prop.tag == make_tag("D", "resourcetype"):
2011-12-31 13:31:22 +01:00
for resource_type in prop:
2018-08-28 16:19:36 +02:00
if resource_type.tag == make_tag("C", "calendar"):
result["tag"] = "VCALENDAR"
2011-12-31 13:31:22 +01:00
break
2018-08-28 16:19:36 +02:00
elif resource_type.tag == make_tag("CR", "addressbook"):
result["tag"] = "VADDRESSBOOK"
break
2018-08-28 16:19:36 +02:00
elif prop.tag == make_tag("C", "supported-calendar-component-set"):
result[tag_from_clark(prop.tag)] = ",".join(
supported_comp.attrib["name"]
for supported_comp in prop
2018-08-28 16:19:36 +02:00
if supported_comp.tag == make_tag("C", "comp"))
else:
2018-08-28 16:19:36 +02:00
result[tag_from_clark(prop.tag)] = prop.text
return result