internal.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. # This file is part of Radicale - CalDAV and CardDAV server
  2. # Copyright © 2017-2018 Unrud <unrud@outlook.com>
  3. #
  4. # This library is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This library is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with Radicale. If not, see <http://www.gnu.org/licenses/>.
  16. """
  17. The default web backend.
  18. Features:
  19. - Create and delete address books and calendars.
  20. - Edit basic metadata of existing address books and calendars.
  21. - Upload address books and calendars from files.
  22. """
  23. import os
  24. import posixpath
  25. import time
  26. from http import client
  27. from typing import Mapping
  28. import pkg_resources
  29. from radicale import config, httputils, pathutils, types, web
  30. from radicale.log import logger
  31. MIMETYPES: Mapping[str, str] = {
  32. ".css": "text/css",
  33. ".eot": "application/vnd.ms-fontobject",
  34. ".gif": "image/gif",
  35. ".html": "text/html",
  36. ".js": "application/javascript",
  37. ".manifest": "text/cache-manifest",
  38. ".png": "image/png",
  39. ".svg": "image/svg+xml",
  40. ".ttf": "application/font-sfnt",
  41. ".txt": "text/plain",
  42. ".woff": "application/font-woff",
  43. ".woff2": "font/woff2",
  44. ".xml": "text/xml"}
  45. FALLBACK_MIMETYPE: str = "application/octet-stream"
  46. class Web(web.BaseWeb):
  47. folder: str
  48. def __init__(self, configuration: config.Configuration) -> None:
  49. super().__init__(configuration)
  50. self.folder = pkg_resources.resource_filename(__name__,
  51. "internal_data")
  52. def get(self, environ: types.WSGIEnviron, base_prefix: str, path: str,
  53. user: str) -> types.WSGIResponse:
  54. assert path == "/.web" or path.startswith("/.web/")
  55. assert pathutils.sanitize_path(path) == path
  56. try:
  57. filesystem_path = pathutils.path_to_filesystem(
  58. self.folder, path[len("/.web"):].strip("/"))
  59. except ValueError as e:
  60. logger.debug("Web content with unsafe path %r requested: %s",
  61. path, e, exc_info=True)
  62. return httputils.NOT_FOUND
  63. if os.path.isdir(filesystem_path) and not path.endswith("/"):
  64. location = posixpath.basename(path) + "/"
  65. return (client.FOUND,
  66. {"Location": location, "Content-Type": "text/plain"},
  67. "Redirected to %s" % location)
  68. if os.path.isdir(filesystem_path):
  69. filesystem_path = os.path.join(filesystem_path, "index.html")
  70. if not os.path.isfile(filesystem_path):
  71. return httputils.NOT_FOUND
  72. content_type = MIMETYPES.get(
  73. os.path.splitext(filesystem_path)[1].lower(), FALLBACK_MIMETYPE)
  74. with open(filesystem_path, "rb") as f:
  75. answer = f.read()
  76. last_modified = time.strftime(
  77. "%a, %d %b %Y %H:%M:%S GMT",
  78. time.gmtime(os.fstat(f.fileno()).st_mtime))
  79. headers = {
  80. "Content-Type": content_type,
  81. "Last-Modified": last_modified}
  82. return client.OK, headers, answer