get.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  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 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_attachement(self, filename: str) -> str:
  42. value = "attachement"
  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 URL is requested
  56. if not pathutils.strip_path(path):
  57. web_path = ".web"
  58. if not environ.get("PATH_INFO"):
  59. web_path = posixpath.join(posixpath.basename(base_prefix),
  60. web_path)
  61. return (client.FOUND,
  62. {"Location": web_path, "Content-Type": "text/plain"},
  63. "Redirected to %s" % web_path)
  64. # Dispatch .web URL to web module
  65. if path == "/.web" or path.startswith("/.web/"):
  66. return self._web.get(environ, base_prefix, path, user)
  67. access = Access(self._rights, user, path)
  68. if not access.check("r") and "i" not in access.permissions:
  69. return httputils.NOT_ALLOWED
  70. with self._storage.acquire_lock("r", user):
  71. item = next(iter(self._storage.discover(path)), None)
  72. if not item:
  73. return httputils.NOT_FOUND
  74. if access.check("r", item):
  75. limited_access = False
  76. elif "i" in access.permissions:
  77. limited_access = True
  78. else:
  79. return httputils.NOT_ALLOWED
  80. if isinstance(item, storage.BaseCollection):
  81. if not item.tag:
  82. return (httputils.NOT_ALLOWED if limited_access else
  83. httputils.DIRECTORY_LISTING)
  84. content_type = xmlutils.MIMETYPES[item.tag]
  85. content_disposition = self._content_disposition_attachement(
  86. propose_filename(item))
  87. elif limited_access:
  88. return httputils.NOT_ALLOWED
  89. else:
  90. content_type = xmlutils.OBJECT_MIMETYPES[item.name]
  91. content_disposition = ""
  92. assert item.last_modified
  93. headers = {
  94. "Content-Type": content_type,
  95. "Last-Modified": item.last_modified,
  96. "ETag": item.etag}
  97. if content_disposition:
  98. headers["Content-Disposition"] = content_disposition
  99. answer = item.serialize()
  100. return client.OK, headers, answer