]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/controllers/home.py
import quincy beta 17.1.0
[ceph.git] / ceph / src / pybind / mgr / dashboard / controllers / home.py
1 # -*- coding: utf-8 -*-
2
3 import json
4 import logging
5 import os
6 import re
7
8 try:
9 from functools import lru_cache
10 except ImportError:
11 from ..plugins.lru_cache import lru_cache
12
13 import cherrypy
14 from cherrypy.lib.static import serve_file
15
16 from .. import mgr
17 from . import BaseController, Endpoint, Proxy, Router, UIRouter
18
19 logger = logging.getLogger("controllers.home")
20
21
22 class LanguageMixin(object):
23 def __init__(self):
24 try:
25 self.LANGUAGES = {
26 f
27 for f in os.listdir(mgr.get_frontend_path())
28 if os.path.isdir(os.path.join(mgr.get_frontend_path(), f))
29 }
30 except FileNotFoundError:
31 logger.exception("Build directory missing")
32 self.LANGUAGES = {}
33
34 self.LANGUAGES_PATH_MAP = {
35 f.lower(): {
36 'lang': f,
37 'path': os.path.join(mgr.get_frontend_path(), f)
38 }
39 for f in self.LANGUAGES
40 }
41 # pre-populating with the primary language subtag.
42 for lang in list(self.LANGUAGES_PATH_MAP.keys()):
43 if '-' in lang:
44 self.LANGUAGES_PATH_MAP[lang.split('-')[0]] = {
45 'lang': self.LANGUAGES_PATH_MAP[lang]['lang'],
46 'path': self.LANGUAGES_PATH_MAP[lang]['path']
47 }
48 with open(os.path.normpath("{}/../package.json".format(mgr.get_frontend_path())),
49 "r") as f:
50 config = json.load(f)
51 self.DEFAULT_LANGUAGE = config['config']['locale']
52 self.DEFAULT_LANGUAGE_PATH = os.path.join(mgr.get_frontend_path(),
53 self.DEFAULT_LANGUAGE)
54 super().__init__()
55
56
57 @Router("/", secure=False)
58 class HomeController(BaseController, LanguageMixin):
59 LANG_TAG_SEQ_RE = re.compile(r'\s*([^,]+)\s*,?\s*')
60 LANG_TAG_RE = re.compile(
61 r'^(?P<locale>[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*|\*)(;q=(?P<weight>[01]\.\d{0,3}))?$')
62 MAX_ACCEPTED_LANGS = 10
63
64 @lru_cache()
65 def _parse_accept_language(self, accept_lang_header):
66 result = []
67 for i, m in enumerate(self.LANG_TAG_SEQ_RE.finditer(accept_lang_header)):
68 if i >= self.MAX_ACCEPTED_LANGS:
69 logger.debug("reached max accepted languages, skipping remaining")
70 break
71
72 tag_match = self.LANG_TAG_RE.match(m.group(1))
73 if tag_match is None:
74 raise cherrypy.HTTPError(400, "Malformed 'Accept-Language' header")
75 locale = tag_match.group('locale').lower()
76 weight = tag_match.group('weight')
77 if weight:
78 try:
79 ratio = float(weight)
80 except ValueError:
81 raise cherrypy.HTTPError(400, "Malformed 'Accept-Language' header")
82 else:
83 ratio = 1.0
84 result.append((locale, ratio))
85
86 result.sort(key=lambda l: l[0])
87 result.sort(key=lambda l: l[1], reverse=True)
88 logger.debug("language preference: %s", result)
89 return [r[0] for r in result]
90
91 def _language_dir(self, langs):
92 for lang in langs:
93 if lang in self.LANGUAGES_PATH_MAP:
94 logger.debug("found directory for language '%s'", lang)
95 cherrypy.response.headers[
96 'Content-Language'] = self.LANGUAGES_PATH_MAP[lang]['lang']
97 return self.LANGUAGES_PATH_MAP[lang]['path']
98
99 logger.debug("Languages '%s' not available, falling back to %s",
100 langs, self.DEFAULT_LANGUAGE)
101 cherrypy.response.headers['Content-Language'] = self.DEFAULT_LANGUAGE
102 return self.DEFAULT_LANGUAGE_PATH
103
104 @Proxy()
105 def __call__(self, path, **params):
106 if not path:
107 path = "index.html"
108
109 if 'cd-lang' in cherrypy.request.cookie:
110 langs = [cherrypy.request.cookie['cd-lang'].value.lower()]
111 logger.debug("frontend language from cookie: %s", langs)
112 else:
113 if 'Accept-Language' in cherrypy.request.headers:
114 accept_lang_header = cherrypy.request.headers['Accept-Language']
115 langs = self._parse_accept_language(accept_lang_header)
116 else:
117 langs = [self.DEFAULT_LANGUAGE.lower()]
118 logger.debug("frontend language from headers: %s", langs)
119
120 base_dir = self._language_dir(langs)
121 full_path = os.path.join(base_dir, path)
122
123 # Block uplevel attacks
124 if not os.path.normpath(full_path).startswith(os.path.normpath(base_dir)):
125 raise cherrypy.HTTPError(403) # Forbidden
126
127 logger.debug("serving static content: %s", full_path)
128 if 'Vary' in cherrypy.response.headers:
129 cherrypy.response.headers['Vary'] = "{}, Accept-Language"
130 else:
131 cherrypy.response.headers['Vary'] = "Accept-Language"
132
133 cherrypy.response.headers['Cache-control'] = "no-cache"
134 return serve_file(full_path)
135
136
137 @UIRouter("/langs", secure=False)
138 class LangsController(BaseController, LanguageMixin):
139 @Endpoint('GET')
140 def __call__(self):
141 return list(self.LANGUAGES)