internal.py 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. # This file is part of Radicale Server - Calendar 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. import pkg_resources
  28. from radicale import httputils, pathutils, web
  29. from radicale.log import logger
  30. MIMETYPES = {
  31. ".css": "text/css",
  32. ".eot": "application/vnd.ms-fontobject",
  33. ".gif": "image/gif",
  34. ".html": "text/html",
  35. ".js": "application/javascript",
  36. ".manifest": "text/cache-manifest",
  37. ".png": "image/png",
  38. ".svg": "image/svg+xml",
  39. ".ttf": "application/font-sfnt",
  40. ".txt": "text/plain",
  41. ".woff": "application/font-woff",
  42. ".woff2": "font/woff2",
  43. ".xml": "text/xml"}
  44. FALLBACK_MIMETYPE = "application/octet-stream"
  45. class Web(web.BaseWeb):
  46. def __init__(self, configuration):
  47. super().__init__(configuration)
  48. self.folder = pkg_resources.resource_filename(__name__,
  49. "internal_data")
  50. def get(self, environ, base_prefix, path, user):
  51. assert path == "/.web" or path.startswith("/.web/")
  52. assert pathutils.sanitize_path(path) == path
  53. try:
  54. filesystem_path = pathutils.path_to_filesystem(
  55. self.folder, path[len("/.web"):].strip("/"))
  56. except ValueError as e:
  57. logger.debug("Web content with unsafe path %r requested: %s",
  58. path, e, exc_info=True)
  59. return httputils.NOT_FOUND
  60. if os.path.isdir(filesystem_path) and not path.endswith("/"):
  61. location = posixpath.basename(path) + "/"
  62. return (client.FOUND,
  63. {"Location": location, "Content-Type": "text/plain"},
  64. "Redirected to %s" % location)
  65. if os.path.isdir(filesystem_path):
  66. filesystem_path = os.path.join(filesystem_path, "index.html")
  67. if not os.path.isfile(filesystem_path):
  68. return httputils.NOT_FOUND
  69. content_type = MIMETYPES.get(
  70. os.path.splitext(filesystem_path)[1].lower(), FALLBACK_MIMETYPE)
  71. with open(filesystem_path, "rb") as f:
  72. answer = f.read()
  73. last_modified = time.strftime(
  74. "%a, %d %b %Y %H:%M:%S GMT",
  75. time.gmtime(os.fstat(f.fileno()).st_mtime))
  76. headers = {
  77. "Content-Type": content_type,
  78. "Last-Modified": last_modified}
  79. return client.OK, headers, answer