proppatch.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. # This file is part of Radicale Server - Calendar Server
  2. # Copyright © 2008 Nicolas Kandel
  3. # Copyright © 2008 Pascal Halter
  4. # Copyright © 2008-2017 Guillaume Ayoub
  5. # Copyright © 2017-2018 Unrud <unrud@outlook.com>
  6. #
  7. # This library is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This library is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with Radicale. If not, see <http://www.gnu.org/licenses/>.
  19. import socket
  20. from http import client
  21. from xml.etree import ElementTree as ET
  22. import defusedxml.ElementTree as DefusedET
  23. from radicale import app, httputils
  24. from radicale import item as radicale_item
  25. from radicale import storage, xmlutils
  26. from radicale.hook import HookNotificationItem, HookNotificationItemTypes
  27. from radicale.log import logger
  28. def xml_add_propstat_to(element, tag, status_number):
  29. """Add a PROPSTAT response structure to an element.
  30. The PROPSTAT answer structure is defined in rfc4918-9.1. It is added to the
  31. given ``element``, for the following ``tag`` with the given
  32. ``status_number``.
  33. """
  34. propstat = ET.Element(xmlutils.make_clark("D:propstat"))
  35. element.append(propstat)
  36. prop = ET.Element(xmlutils.make_clark("D:prop"))
  37. propstat.append(prop)
  38. clark_tag = xmlutils.make_clark(tag)
  39. prop_tag = ET.Element(clark_tag)
  40. prop.append(prop_tag)
  41. status = ET.Element(xmlutils.make_clark("D:status"))
  42. status.text = xmlutils.make_response(status_number)
  43. propstat.append(status)
  44. def xml_proppatch(base_prefix, path, xml_request, collection):
  45. """Read and answer PROPPATCH requests.
  46. Read rfc4918-9.2 for info.
  47. """
  48. props_to_set = xmlutils.props_from_request(xml_request, actions=("set",))
  49. props_to_remove = xmlutils.props_from_request(xml_request,
  50. actions=("remove",))
  51. multistatus = ET.Element(xmlutils.make_clark("D:multistatus"))
  52. response = ET.Element(xmlutils.make_clark("D:response"))
  53. multistatus.append(response)
  54. href = ET.Element(xmlutils.make_clark("D:href"))
  55. href.text = xmlutils.make_href(base_prefix, path)
  56. response.append(href)
  57. new_props = collection.get_meta()
  58. for short_name, value in props_to_set.items():
  59. new_props[short_name] = value
  60. xml_add_propstat_to(response, short_name, 200)
  61. for short_name in props_to_remove:
  62. try:
  63. del new_props[short_name]
  64. except KeyError:
  65. pass
  66. xml_add_propstat_to(response, short_name, 200)
  67. radicale_item.check_and_sanitize_props(new_props)
  68. collection.set_meta(new_props)
  69. return multistatus
  70. class ApplicationProppatchMixin:
  71. def do_PROPPATCH(self, environ, base_prefix, path, user):
  72. """Manage PROPPATCH request."""
  73. access = app.Access(self._rights, user, path)
  74. if not access.check("w"):
  75. return httputils.NOT_ALLOWED
  76. try:
  77. xml_content = self._read_xml_content(environ)
  78. except RuntimeError as e:
  79. logger.warning(
  80. "Bad PROPPATCH request on %r: %s", path, e, exc_info=True)
  81. return httputils.BAD_REQUEST
  82. except socket.timeout:
  83. logger.debug("client timed out", exc_info=True)
  84. return httputils.REQUEST_TIMEOUT
  85. with self._storage.acquire_lock("w", user):
  86. item = next(self._storage.discover(path), None)
  87. if not item:
  88. return httputils.NOT_FOUND
  89. if not access.check("w", item):
  90. return httputils.NOT_ALLOWED
  91. if not isinstance(item, storage.BaseCollection):
  92. return httputils.FORBIDDEN
  93. headers = {"DAV": httputils.DAV_HEADERS,
  94. "Content-Type": "text/xml; charset=%s" % self._encoding}
  95. try:
  96. hook_notification_item = HookNotificationItem(
  97. HookNotificationItemTypes.CPATCH,
  98. access.path,
  99. DefusedET.tostring(
  100. xml_content,
  101. encoding=self._encoding
  102. ).decode(encoding=self._encoding)
  103. )
  104. xml_answer = xml_proppatch(base_prefix, path, xml_content,
  105. item)
  106. self._hook.notify(hook_notification_item)
  107. except ValueError as e:
  108. logger.warning(
  109. "Bad PROPPATCH request on %r: %s", path, e, exc_info=True)
  110. return httputils.BAD_REQUEST
  111. return (client.MULTI_STATUS, headers,
  112. self._write_xml_content(xml_answer))