move.py 5.1 KB

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