web.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. # This file is part of Radicale Server - Calendar Server
  2. # Copyright (C) 2017 Unrud <unrud@openaliasbox.org>
  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. import os
  17. import posixpath
  18. import time
  19. from http import client
  20. from importlib import import_module
  21. import pkg_resources
  22. from . import storage
  23. NOT_FOUND = (
  24. client.NOT_FOUND, (("Content-Type", "text/plain"),),
  25. "The requested resource could not be found.")
  26. MIMETYPES = {
  27. ".css": "text/css",
  28. ".eot": "application/vnd.ms-fontobject",
  29. ".gif": "image/gif",
  30. ".html": "text/html",
  31. ".js": "application/javascript",
  32. ".manifest": "text/cache-manifest",
  33. ".png": "image/png",
  34. ".svg": "image/svg+xml",
  35. ".ttf": "application/font-sfnt",
  36. ".txt": "text/plain",
  37. ".woff": "application/font-woff",
  38. ".woff2": "font/woff2",
  39. ".xml": "text/xml"}
  40. FALLBACK_MIMETYPE = "application/octet-stream"
  41. def load(configuration, logger):
  42. """Load the web module chosen in configuration."""
  43. web_type = configuration.get("web", "type")
  44. if web_type in ("None", "none"): # DEPRECATED: use "none"
  45. web_class = NoneWeb
  46. elif web_type == "internal":
  47. web_class = Web
  48. else:
  49. try:
  50. web_class = import_module(web_type).Web
  51. except ImportError as e:
  52. raise RuntimeError("Web module %r not found" %
  53. web_type) from e
  54. logger.info("Web type is %r", web_type)
  55. return web_class(configuration, logger)
  56. class BaseWeb:
  57. def __init__(self, configuration, logger):
  58. self.configuration = configuration
  59. self.logger = logger
  60. class NoneWeb(BaseWeb):
  61. def get(self, environ, base_prefix, path, user):
  62. if path != "/.web":
  63. return NOT_FOUND
  64. return client.OK, {"Content-Type": "text/plain"}, "Radicale works!"
  65. class Web(BaseWeb):
  66. def __init__(self, configuration, logger):
  67. super().__init__(configuration, logger)
  68. self.folder = pkg_resources.resource_filename(__name__, "web")
  69. def get(self, environ, base_prefix, path, user):
  70. try:
  71. filesystem_path = storage.path_to_filesystem(
  72. self.folder, path[len("/.web"):])
  73. except ValueError as e:
  74. self.logger.debug("Web content with unsafe path %r requested: %s",
  75. path, e, exc_info=True)
  76. return NOT_FOUND
  77. if os.path.isdir(filesystem_path) and not path.endswith("/"):
  78. location = posixpath.basename(path) + "/"
  79. return (client.SEE_OTHER,
  80. {"Location": location, "Content-Type": "text/plain"},
  81. "Redirected to %s" % location)
  82. if os.path.isdir(filesystem_path):
  83. filesystem_path = os.path.join(filesystem_path, "index.html")
  84. if not os.path.isfile(filesystem_path):
  85. return NOT_FOUND
  86. content_type = MIMETYPES.get(
  87. os.path.splitext(filesystem_path)[1].lower(), FALLBACK_MIMETYPE)
  88. with open(filesystem_path, "rb") as f:
  89. answer = f.read()
  90. last_modified = time.strftime(
  91. "%a, %d %b %Y %H:%M:%S GMT",
  92. time.gmtime(os.fstat(f.fileno()).st_mtime))
  93. headers = {
  94. "Content-Type": content_type,
  95. "Last-Modified": last_modified}
  96. return client.OK, headers, answer