templates.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. # Copyright 2021 The Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """Utilities for dealing with jinja2 templates"""
  15. import time
  16. import urllib.parse
  17. from typing import TYPE_CHECKING, Callable, Optional, Sequence, Union
  18. import jinja2
  19. if TYPE_CHECKING:
  20. from synapse.config.homeserver import HomeServerConfig
  21. def build_jinja_env(
  22. template_search_directories: Sequence[str],
  23. config: "HomeServerConfig",
  24. autoescape: Union[bool, Callable[[Optional[str]], bool], None] = None,
  25. ) -> jinja2.Environment:
  26. """Set up a Jinja2 environment to load templates from the given search path
  27. The returned environment defines the following filters:
  28. - format_ts: formats timestamps as strings in the server's local timezone
  29. (XXX: why is that useful??)
  30. - mxc_to_http: converts mxc: uris to http URIs. Args are:
  31. (uri, width, height, resize_method="crop")
  32. and the following global variables:
  33. - server_name: matrix server name
  34. Args:
  35. template_search_directories: directories to search for templates
  36. config: homeserver config, for things like `server_name` and `public_baseurl`
  37. autoescape: whether template variables should be autoescaped. bool, or
  38. a function mapping from template name to bool. Defaults to escaping templates
  39. whose names end in .html, .xml or .htm.
  40. Returns:
  41. jinja environment
  42. """
  43. if autoescape is None:
  44. autoescape = jinja2.select_autoescape()
  45. loader = jinja2.FileSystemLoader(template_search_directories)
  46. env = jinja2.Environment(loader=loader, autoescape=autoescape)
  47. # Update the environment with our custom filters
  48. env.filters.update(
  49. {
  50. "format_ts": _format_ts_filter,
  51. "mxc_to_http": _create_mxc_to_http_filter(config.server.public_baseurl),
  52. "localpart_from_email": _localpart_from_email_filter,
  53. }
  54. )
  55. # common variables for all templates
  56. env.globals.update({"server_name": config.server.server_name})
  57. return env
  58. def _create_mxc_to_http_filter(
  59. public_baseurl: Optional[str],
  60. ) -> Callable[[str, int, int, str], str]:
  61. """Create and return a jinja2 filter that converts MXC urls to HTTP
  62. Args:
  63. public_baseurl: The public, accessible base URL of the homeserver
  64. """
  65. def mxc_to_http_filter(
  66. value: str, width: int, height: int, resize_method: str = "crop"
  67. ) -> str:
  68. if not public_baseurl:
  69. raise RuntimeError(
  70. "public_baseurl must be set in the homeserver config to convert MXC URLs to HTTP URLs."
  71. )
  72. if value[0:6] != "mxc://":
  73. return ""
  74. server_and_media_id = value[6:]
  75. fragment = None
  76. if "#" in server_and_media_id:
  77. server_and_media_id, fragment = server_and_media_id.split("#", 1)
  78. fragment = "#" + fragment
  79. params = {"width": width, "height": height, "method": resize_method}
  80. return "%s_matrix/media/v1/thumbnail/%s?%s%s" % (
  81. public_baseurl,
  82. server_and_media_id,
  83. urllib.parse.urlencode(params),
  84. fragment or "",
  85. )
  86. return mxc_to_http_filter
  87. def _format_ts_filter(value: int, format: str) -> str:
  88. return time.strftime(format, time.localtime(value / 1000))
  89. def _localpart_from_email_filter(address: str) -> str:
  90. return address.rsplit("@", 1)[0]