move.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. # This file is part of Radicale - CalDAV and CardDAV 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 posixpath
  20. import re
  21. from http import client
  22. from urllib.parse import urlparse
  23. from radicale import httputils, pathutils, storage, types
  24. from radicale.app.base import Access, ApplicationBase
  25. from radicale.log import logger
  26. def get_server_netloc(environ: types.WSGIEnviron, force_port: bool = False):
  27. if environ.get("HTTP_X_FORWARDED_HOST"):
  28. host = environ["HTTP_X_FORWARDED_HOST"]
  29. proto = environ.get("HTTP_X_FORWARDED_PROTO") or "http"
  30. port = "443" if proto == "https" else "80"
  31. port = environ["HTTP_X_FORWARDED_PORT"] or port
  32. else:
  33. host = environ.get("HTTP_HOST") or environ["SERVER_NAME"]
  34. proto = environ["wsgi.url_scheme"]
  35. port = environ["SERVER_PORT"]
  36. if (not force_port and port == ("443" if proto == "https" else "80") or
  37. re.search(r":\d+$", host)):
  38. return host
  39. return host + ":" + port
  40. class ApplicationPartMove(ApplicationBase):
  41. def do_MOVE(self, environ: types.WSGIEnviron, base_prefix: str,
  42. path: str, user: str) -> types.WSGIResponse:
  43. """Manage MOVE request."""
  44. raw_dest = environ.get("HTTP_DESTINATION", "")
  45. to_url = urlparse(raw_dest)
  46. to_netloc_with_port = to_url.netloc
  47. if to_url.port is None:
  48. to_netloc_with_port += (":443" if to_url.scheme == "https"
  49. else ":80")
  50. if to_netloc_with_port != get_server_netloc(environ, force_port=True):
  51. logger.info("Unsupported destination address: %r", raw_dest)
  52. # Remote destination server, not supported
  53. return httputils.REMOTE_DESTINATION
  54. access = Access(self._rights, user, path)
  55. if not access.check("w"):
  56. return httputils.NOT_ALLOWED
  57. to_path = pathutils.sanitize_path(to_url.path)
  58. if not (to_path + "/").startswith(base_prefix + "/"):
  59. logger.warning("Destination %r from MOVE request on %r doesn't "
  60. "start with base prefix", to_path, path)
  61. return httputils.NOT_ALLOWED
  62. to_path = to_path[len(base_prefix):]
  63. to_access = Access(self._rights, user, to_path)
  64. if not to_access.check("w"):
  65. return httputils.NOT_ALLOWED
  66. with self._storage.acquire_lock("w", user):
  67. item = next(iter(self._storage.discover(path)), None)
  68. if not item:
  69. return httputils.NOT_FOUND
  70. if (not access.check("w", item) or
  71. not to_access.check("w", item)):
  72. return httputils.NOT_ALLOWED
  73. if isinstance(item, storage.BaseCollection):
  74. # TODO: support moving collections
  75. return httputils.METHOD_NOT_ALLOWED
  76. to_item = next(iter(self._storage.discover(to_path)), None)
  77. if isinstance(to_item, storage.BaseCollection):
  78. return httputils.FORBIDDEN
  79. to_parent_path = pathutils.unstrip_path(
  80. posixpath.dirname(pathutils.strip_path(to_path)), True)
  81. to_collection = next(iter(
  82. self._storage.discover(to_parent_path)), None)
  83. if not to_collection:
  84. return httputils.CONFLICT
  85. assert isinstance(to_collection, storage.BaseCollection)
  86. assert item.collection is not None
  87. collection_tag = item.collection.tag
  88. if not collection_tag or collection_tag != to_collection.tag:
  89. return httputils.FORBIDDEN
  90. if to_item and environ.get("HTTP_OVERWRITE", "F") != "T":
  91. return httputils.PRECONDITION_FAILED
  92. if (to_item and item.uid != to_item.uid or
  93. not to_item and
  94. to_collection.path != item.collection.path and
  95. to_collection.has_uid(item.uid)):
  96. return self._webdav_error_response(
  97. client.CONFLICT, "%s:no-uid-conflict" % (
  98. "C" if collection_tag == "VCALENDAR" else "CR"))
  99. to_href = posixpath.basename(pathutils.strip_path(to_path))
  100. try:
  101. self._storage.move(item, to_collection, to_href)
  102. except ValueError as e:
  103. logger.warning(
  104. "Bad MOVE request on %r: %s", path, e, exc_info=True)
  105. return httputils.BAD_REQUEST
  106. return client.NO_CONTENT if to_item else client.CREATED, {}, None