get.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  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. from http import client
  21. from urllib.parse import quote
  22. from radicale import httputils, pathutils, storage, types, xmlutils
  23. from radicale.app.base import Access, ApplicationBase
  24. from radicale.log import logger
  25. def propose_filename(collection: storage.BaseCollection) -> str:
  26. """Propose a filename for a collection."""
  27. if collection.tag == "VADDRESSBOOK":
  28. fallback_title = "Address book"
  29. suffix = ".vcf"
  30. elif collection.tag == "VCALENDAR":
  31. fallback_title = "Calendar"
  32. suffix = ".ics"
  33. else:
  34. fallback_title = posixpath.basename(collection.path)
  35. suffix = ""
  36. title = collection.get_meta("D:displayname") or fallback_title
  37. if title and not title.lower().endswith(suffix.lower()):
  38. title += suffix
  39. return title
  40. class ApplicationPartGet(ApplicationBase):
  41. def _content_disposition_attachment(self, filename: str) -> str:
  42. value = "attachment"
  43. try:
  44. encoded_filename = quote(filename, encoding=self._encoding)
  45. except UnicodeEncodeError:
  46. logger.warning("Failed to encode filename: %r", filename,
  47. exc_info=True)
  48. encoded_filename = ""
  49. if encoded_filename:
  50. value += "; filename*=%s''%s" % (self._encoding, encoded_filename)
  51. return value
  52. def do_GET(self, environ: types.WSGIEnviron, base_prefix: str, path: str,
  53. user: str) -> types.WSGIResponse:
  54. """Manage GET request."""
  55. # Redirect to /.web if the root path is requested
  56. if not pathutils.strip_path(path):
  57. return httputils.redirect(base_prefix + "/.web")
  58. if path == "/.web" or path.startswith("/.web/"):
  59. # Redirect to sanitized path for all subpaths of /.web
  60. unsafe_path = environ.get("PATH_INFO", "")
  61. if unsafe_path != path:
  62. location = base_prefix + path
  63. logger.info("Redirecting to sanitized path: %r ==> %r",
  64. base_prefix + unsafe_path, location)
  65. return httputils.redirect(location, client.MOVED_PERMANENTLY)
  66. # Dispatch /.web path to web module
  67. return self._web.get(environ, base_prefix, path, user)
  68. access = Access(self._rights, user, path)
  69. if not access.check("r") and "i" not in access.permissions:
  70. return httputils.NOT_ALLOWED
  71. with self._storage.acquire_lock("r", user):
  72. item = next(iter(self._storage.discover(path)), None)
  73. if not item:
  74. return httputils.NOT_FOUND
  75. if access.check("r", item):
  76. limited_access = False
  77. elif "i" in access.permissions:
  78. limited_access = True
  79. else:
  80. return httputils.NOT_ALLOWED
  81. if isinstance(item, storage.BaseCollection):
  82. if not item.tag:
  83. return (httputils.NOT_ALLOWED if limited_access else
  84. httputils.DIRECTORY_LISTING)
  85. content_type = xmlutils.MIMETYPES[item.tag]
  86. content_disposition = self._content_disposition_attachment(
  87. propose_filename(item))
  88. elif limited_access:
  89. return httputils.NOT_ALLOWED
  90. else:
  91. content_type = xmlutils.OBJECT_MIMETYPES[item.name]
  92. content_disposition = ""
  93. assert item.last_modified
  94. headers = {
  95. "Content-Type": content_type,
  96. "Last-Modified": item.last_modified,
  97. "ETag": item.etag}
  98. if content_disposition:
  99. headers["Content-Disposition"] = content_disposition
  100. answer = item.serialize()
  101. return client.OK, headers, answer