We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Make 404 more useful when possible
### Describe the problem

For example, if you try go to a non-existent language of a component, the page will only suggest to go home or to all projects, while it would be better if it linked to the component, or suggested adding a language that you tried to visit. Example: https://hosted.weblate.org/projects/keepassxc/keepassxc-browser-dev/fil/

![image](https://user-images.githubusercontent.com/39462442/170858920-68e686b2-649b-40ce-8d04-c9f5de9b24fb.png)


### Describe the solution you'd like

Add useful suggestions depending on the context.

### Describe alternatives you've considered

_No response_

### Screenshots

_No response_

### Additional context

https://github.com/WeblateOrg/weblate/blob/1804a951c8ae9199d23099a8ec322134ee5b45bc/weblate/urls.py#L136-L140

https://github.com/WeblateOrg/weblate/blob/329dabf59991ed2ee1b44f331929da41d8031c98/weblate/templates/404.html

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `weblate/middleware.py`
Content:
```
1 #
2 # Copyright © 2012–2022 Michal Čihař <michal@cihar.com>
3 #
4 # This file is part of Weblate <https://weblate.org/>
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
18 #
19 
20 from urllib.parse import urlparse
21 
22 from django.conf import settings
23 from django.core.exceptions import ValidationError
24 from django.core.validators import validate_ipv46_address
25 from django.http import Http404, HttpResponsePermanentRedirect
26 from django.urls import is_valid_path, reverse
27 from django.utils.http import escape_leading_slashes
28 
29 from weblate.lang.models import Language
30 from weblate.trans.models import Change, Component, Project
31 from weblate.utils.errors import report_error
32 from weblate.utils.site import get_site_url
33 
34 CSP_TEMPLATE = (
35     "default-src 'self'; style-src {0}; img-src {1}; script-src {2}; "
36     "connect-src {3}; object-src 'none'; font-src {4};"
37     "frame-src 'none'; frame-ancestors 'none';"
38 )
39 
40 # URLs requiring inline javascript
41 INLINE_PATHS = {"social:begin", "djangosaml2idp:saml_login_process"}
42 
43 
44 class ProxyMiddleware:
45     """Middleware that updates REMOTE_ADDR from proxy.
46 
47     Note that this can have security implications and settings have to match your actual
48     proxy setup.
49     """
50 
51     def __init__(self, get_response=None):
52         self.get_response = get_response
53 
54     def __call__(self, request):
55         # Fake HttpRequest attribute to inject configured
56         # site name into build_absolute_uri
57         request._current_scheme_host = get_site_url()
58 
59         # Actual proxy handling
60         proxy = None
61         if settings.IP_BEHIND_REVERSE_PROXY:
62             proxy = request.META.get(settings.IP_PROXY_HEADER)
63         if proxy:
64             # X_FORWARDED_FOR returns client1, proxy1, proxy2,...
65             address = proxy.split(",")[settings.IP_PROXY_OFFSET].strip()
66             try:
67                 validate_ipv46_address(address)
68                 request.META["REMOTE_ADDR"] = address
69             except ValidationError:
70                 report_error(cause="Invalid IP address")
71 
72         return self.get_response(request)
73 
74 
75 class RedirectMiddleware:
76     """
77     Middleware that handles URL redirecting.
78 
79     This used for fuzzy lookups of projects, for example case insensitive
80     or after renaming.
81     """
82 
83     def __init__(self, get_response=None):
84         self.get_response = get_response
85 
86     def __call__(self, request):
87         response = self.get_response(request)
88         # This is based on APPEND_SLASH handling in Django
89         if response.status_code == 404 and self.should_redirect_with_slash(request):
90             new_path = request.get_full_path(force_append_slash=True)
91             # Prevent construction of scheme relative urls.
92             new_path = escape_leading_slashes(new_path)
93             return HttpResponsePermanentRedirect(new_path)
94         return response
95 
96     def should_redirect_with_slash(self, request):
97         path = request.path_info
98         # Avoid redirecting non GET requests, these would fail anyway due to
99         # missing parameters.
100         # Redirecting on API removes authentication headers in many cases,
101         # so avoid that as well.
102         if (
103             path.endswith("/")
104             or request.method != "GET"
105             or path.startswith(f"{settings.URL_PREFIX}/api")
106         ):
107             return False
108         urlconf = getattr(request, "urlconf", None)
109         slash_path = f"{path}/"
110         return not is_valid_path(path, urlconf) and is_valid_path(slash_path, urlconf)
111 
112     def fixup_language(self, lang):
113         return Language.objects.fuzzy_get(code=lang, strict=True)
114 
115     def fixup_project(self, slug, request):
116         try:
117             project = Project.objects.get(slug__iexact=slug)
118         except Project.MultipleObjectsReturned:
119             return None
120         except Project.DoesNotExist:
121             try:
122                 project = (
123                     Change.objects.filter(
124                         action=Change.ACTION_RENAME_PROJECT,
125                         old=slug,
126                     )
127                     .order()[0]
128                     .project
129                 )
130             except IndexError:
131                 return None
132 
133         request.user.check_access(project)
134         return project
135 
136     def fixup_component(self, slug, request, project):
137         try:
138             component = Component.objects.get(project=project, slug__iexact=slug)
139         except Component.DoesNotExist:
140             try:
141                 component = (
142                     Change.objects.filter(
143                         action=Change.ACTION_RENAME_COMPONENT, old=slug
144                     )
145                     .order()[0]
146                     .component
147                 )
148             except IndexError:
149                 return None
150 
151         request.user.check_access_component(component)
152         return component
153 
154     def process_exception(self, request, exception):
155         if not isinstance(exception, Http404):
156             return None
157 
158         try:
159             resolver_match = request.resolver_match
160         except AttributeError:
161             return None
162 
163         kwargs = dict(resolver_match.kwargs)
164 
165         if "lang" in kwargs:
166             language = self.fixup_language(kwargs["lang"])
167             if language is None:
168                 return None
169             kwargs["lang"] = language.code
170 
171         if "project" in kwargs:
172             project = self.fixup_project(kwargs["project"], request)
173             if project is None:
174                 return None
175             kwargs["project"] = project.slug
176 
177             if "component" in kwargs:
178                 component = self.fixup_component(kwargs["component"], request, project)
179                 if component is None:
180                     return None
181                 kwargs["component"] = component.slug
182 
183         if kwargs != resolver_match.kwargs:
184             query = request.META["QUERY_STRING"]
185             if query:
186                 query = f"?{query}"
187             return HttpResponsePermanentRedirect(
188                 reverse(resolver_match.url_name, kwargs=kwargs) + query
189             )
190 
191         return None
192 
193 
194 class SecurityMiddleware:
195     """Middleware that sets Content-Security-Policy."""
196 
197     def __init__(self, get_response=None):
198         self.get_response = get_response
199 
200     def __call__(self, request):
201         response = self.get_response(request)
202         # No CSP for debug mode (to allow djdt or error pages)
203         if settings.DEBUG:
204             return response
205 
206         style = {"'self'", "'unsafe-inline'"} | set(settings.CSP_STYLE_SRC)
207         script = {"'self'"} | set(settings.CSP_SCRIPT_SRC)
208         image = {"'self'"} | set(settings.CSP_IMG_SRC)
209         connect = {"'self'"} | set(settings.CSP_CONNECT_SRC)
210         font = {"'self'"} | set(settings.CSP_FONT_SRC)
211 
212         if request.resolver_match and request.resolver_match.view_name in INLINE_PATHS:
213             script.add("'unsafe-inline'")
214 
215         # Support form
216         if request.resolver_match and request.resolver_match.view_name == "manage":
217             script.add("care.weblate.org")
218             connect.add("care.weblate.org")
219             style.add("care.weblate.org")
220 
221         # Rollbar client errors reporting
222         if (
223             hasattr(settings, "ROLLBAR")
224             and "client_token" in settings.ROLLBAR
225             and "environment" in settings.ROLLBAR
226             and response.status_code == 500
227         ):
228             script.add("'unsafe-inline'")
229             script.add("cdnjs.cloudflare.com")
230             connect.add("api.rollbar.com")
231 
232         # Sentry user feedback
233         if settings.SENTRY_DSN and response.status_code == 500:
234             domain = urlparse(settings.SENTRY_DSN).hostname
235             script.add(domain)
236             script.add("sentry.io")
237             connect.add(domain)
238             connect.add("sentry.io")
239             script.add("'unsafe-inline'")
240             image.add("data:")
241 
242         # Matomo (Piwik) analytics
243         if settings.MATOMO_URL:
244             domain = urlparse(settings.MATOMO_URL).hostname
245             script.add(domain)
246             image.add(domain)
247             connect.add(domain)
248 
249         # Google Analytics
250         if settings.GOOGLE_ANALYTICS_ID:
251             script.add("'unsafe-inline'")
252             script.add("www.google-analytics.com")
253             image.add("www.google-analytics.com")
254 
255         # External media URL
256         if "://" in settings.MEDIA_URL:
257             domain = urlparse(settings.MEDIA_URL).hostname
258             image.add(domain)
259 
260         # External static URL
261         if "://" in settings.STATIC_URL:
262             domain = urlparse(settings.STATIC_URL).hostname
263             script.add(domain)
264             image.add(domain)
265             style.add(domain)
266             font.add(domain)
267 
268         # CDN for fonts
269         if settings.FONTS_CDN_URL:
270             domain = urlparse(settings.FONTS_CDN_URL).hostname
271             style.add(domain)
272             font.add(domain)
273 
274         # When using external image for Auth0 provider, add it here
275         if "://" in settings.SOCIAL_AUTH_AUTH0_IMAGE:
276             domain = urlparse(settings.SOCIAL_AUTH_AUTH0_IMAGE).hostname
277             image.add(domain)
278 
279         response["Content-Security-Policy"] = CSP_TEMPLATE.format(
280             " ".join(style),
281             " ".join(image),
282             " ".join(script),
283             " ".join(connect),
284             " ".join(font),
285         )
286         if settings.SENTRY_SECURITY:
287             response["Content-Security-Policy"] += " report-uri {}".format(
288                 settings.SENTRY_SECURITY
289             )
290             response["Expect-CT"] = 'max-age=86400, enforce, report-uri="{}"'.format(
291                 settings.SENTRY_SECURITY
292             )
293 
294         # Opt-out from Google FLoC
295         response["Permissions-Policy"] = "interest-cohort=()"
296 
297         return response
298
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/weblate/middleware.py b/weblate/middleware.py
--- a/weblate/middleware.py
+++ b/weblate/middleware.py
@@ -20,11 +20,14 @@
 from urllib.parse import urlparse
 
 from django.conf import settings
+from django.contrib import messages
 from django.core.exceptions import ValidationError
 from django.core.validators import validate_ipv46_address
 from django.http import Http404, HttpResponsePermanentRedirect
+from django.shortcuts import redirect
 from django.urls import is_valid_path, reverse
 from django.utils.http import escape_leading_slashes
+from django.utils.translation import gettext_lazy as _
 
 from weblate.lang.models import Language
 from weblate.trans.models import Change, Component, Project
@@ -151,6 +154,13 @@
         request.user.check_access_component(component)
         return component
 
+    def check_existing_translations(self, slug, project):
+        """Check in existing translations for specific language.
+
+        Return False if language translation not present, else True.
+        """
+        return any(lang.name == slug for lang in project.languages)
+
     def process_exception(self, request, exception):
         if not isinstance(exception, Http404):
             return None
@@ -161,12 +171,13 @@
             return None
 
         kwargs = dict(resolver_match.kwargs)
-
+        new_lang = None
         if "lang" in kwargs:
             language = self.fixup_language(kwargs["lang"])
             if language is None:
                 return None
             kwargs["lang"] = language.code
+            new_lang = language.name
 
         if "project" in kwargs:
             project = self.fixup_project(kwargs["project"], request)
@@ -180,6 +191,24 @@
                     return None
                 kwargs["component"] = component.slug
 
+                if new_lang:
+                    existing_trans = self.check_existing_translations(new_lang, project)
+                    if not existing_trans:
+                        messages.add_message(
+                            request,
+                            messages.INFO,
+                            _(
+                                f"{new_lang} translation is currently not available,\
+                                but can be added."
+                            ),
+                        )
+                        return redirect(
+                            reverse(
+                                "component",
+                                args=[kwargs["project"], kwargs["component"]],
+                            )
+                        )
+
         if kwargs != resolver_match.kwargs:
             query = request.META["QUERY_STRING"]
             if query:

{"golden_diff": "diff --git a/weblate/middleware.py b/weblate/middleware.py\n--- a/weblate/middleware.py\n+++ b/weblate/middleware.py\n@@ -20,11 +20,14 @@\n from urllib.parse import urlparse\n \n from django.conf import settings\n+from django.contrib import messages\n from django.core.exceptions import ValidationError\n from django.core.validators import validate_ipv46_address\n from django.http import Http404, HttpResponsePermanentRedirect\n+from django.shortcuts import redirect\n from django.urls import is_valid_path, reverse\n from django.utils.http import escape_leading_slashes\n+from django.utils.translation import gettext_lazy as _\n \n from weblate.lang.models import Language\n from weblate.trans.models import Change, Component, Project\n@@ -151,6 +154,13 @@\n         request.user.check_access_component(component)\n         return component\n \n+    def check_existing_translations(self, slug, project):\n+        \"\"\"Check in existing translations for specific language.\n+\n+        Return False if language translation not present, else True.\n+        \"\"\"\n+        return any(lang.name == slug for lang in project.languages)\n+\n     def process_exception(self, request, exception):\n         if not isinstance(exception, Http404):\n             return None\n@@ -161,12 +171,13 @@\n             return None\n \n         kwargs = dict(resolver_match.kwargs)\n-\n+        new_lang = None\n         if \"lang\" in kwargs:\n             language = self.fixup_language(kwargs[\"lang\"])\n             if language is None:\n                 return None\n             kwargs[\"lang\"] = language.code\n+            new_lang = language.name\n \n         if \"project\" in kwargs:\n             project = self.fixup_project(kwargs[\"project\"], request)\n@@ -180,6 +191,24 @@\n                     return None\n                 kwargs[\"component\"] = component.slug\n \n+                if new_lang:\n+                    existing_trans = self.check_existing_translations(new_lang, project)\n+                    if not existing_trans:\n+                        messages.add_message(\n+                            request,\n+                            messages.INFO,\n+                            _(\n+                                f\"{new_lang} translation is currently not available,\\\n+                                but can be added.\"\n+                            ),\n+                        )\n+                        return redirect(\n+                            reverse(\n+                                \"component\",\n+                                args=[kwargs[\"project\"], kwargs[\"component\"]],\n+                            )\n+                        )\n+\n         if kwargs != resolver_match.kwargs:\n             query = request.META[\"QUERY_STRING\"]\n             if query:\n", "issue": "Make 404 more useful when possible\n### Describe the problem\n\nFor example, if you try go to a non-existent language of a component, the page will only suggest to go home or to all projects, while it would be better if it linked to the component, or suggested adding a language that you tried to visit. Example: https://hosted.weblate.org/projects/keepassxc/keepassxc-browser-dev/fil/\r\n\r\n![image](https://user-images.githubusercontent.com/39462442/170858920-68e686b2-649b-40ce-8d04-c9f5de9b24fb.png)\r\n\n\n### Describe the solution you'd like\n\nAdd useful suggestions depending on the context.\n\n### Describe alternatives you've considered\n\n_No response_\n\n### Screenshots\n\n_No response_\n\n### Additional context\n\nhttps://github.com/WeblateOrg/weblate/blob/1804a951c8ae9199d23099a8ec322134ee5b45bc/weblate/urls.py#L136-L140\r\n\r\nhttps://github.com/WeblateOrg/weblate/blob/329dabf59991ed2ee1b44f331929da41d8031c98/weblate/templates/404.html\n", "before_files": [{"content": "#\n# Copyright \u00a9 2012\u20132022 Michal \u010ciha\u0159 <michal@cihar.com>\n#\n# This file is part of Weblate <https://weblate.org/>\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\nfrom urllib.parse import urlparse\n\nfrom django.conf import settings\nfrom django.core.exceptions import ValidationError\nfrom django.core.validators import validate_ipv46_address\nfrom django.http import Http404, HttpResponsePermanentRedirect\nfrom django.urls import is_valid_path, reverse\nfrom django.utils.http import escape_leading_slashes\n\nfrom weblate.lang.models import Language\nfrom weblate.trans.models import Change, Component, Project\nfrom weblate.utils.errors import report_error\nfrom weblate.utils.site import get_site_url\n\nCSP_TEMPLATE = (\n    \"default-src 'self'; style-src {0}; img-src {1}; script-src {2}; \"\n    \"connect-src {3}; object-src 'none'; font-src {4};\"\n    \"frame-src 'none'; frame-ancestors 'none';\"\n)\n\n# URLs requiring inline javascript\nINLINE_PATHS = {\"social:begin\", \"djangosaml2idp:saml_login_process\"}\n\n\nclass ProxyMiddleware:\n    \"\"\"Middleware that updates REMOTE_ADDR from proxy.\n\n    Note that this can have security implications and settings have to match your actual\n    proxy setup.\n    \"\"\"\n\n    def __init__(self, get_response=None):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        # Fake HttpRequest attribute to inject configured\n        # site name into build_absolute_uri\n        request._current_scheme_host = get_site_url()\n\n        # Actual proxy handling\n        proxy = None\n        if settings.IP_BEHIND_REVERSE_PROXY:\n            proxy = request.META.get(settings.IP_PROXY_HEADER)\n        if proxy:\n            # X_FORWARDED_FOR returns client1, proxy1, proxy2,...\n            address = proxy.split(\",\")[settings.IP_PROXY_OFFSET].strip()\n            try:\n                validate_ipv46_address(address)\n                request.META[\"REMOTE_ADDR\"] = address\n            except ValidationError:\n                report_error(cause=\"Invalid IP address\")\n\n        return self.get_response(request)\n\n\nclass RedirectMiddleware:\n    \"\"\"\n    Middleware that handles URL redirecting.\n\n    This used for fuzzy lookups of projects, for example case insensitive\n    or after renaming.\n    \"\"\"\n\n    def __init__(self, get_response=None):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        response = self.get_response(request)\n        # This is based on APPEND_SLASH handling in Django\n        if response.status_code == 404 and self.should_redirect_with_slash(request):\n            new_path = request.get_full_path(force_append_slash=True)\n            # Prevent construction of scheme relative urls.\n            new_path = escape_leading_slashes(new_path)\n            return HttpResponsePermanentRedirect(new_path)\n        return response\n\n    def should_redirect_with_slash(self, request):\n        path = request.path_info\n        # Avoid redirecting non GET requests, these would fail anyway due to\n        # missing parameters.\n        # Redirecting on API removes authentication headers in many cases,\n        # so avoid that as well.\n        if (\n            path.endswith(\"/\")\n            or request.method != \"GET\"\n            or path.startswith(f\"{settings.URL_PREFIX}/api\")\n        ):\n            return False\n        urlconf = getattr(request, \"urlconf\", None)\n        slash_path = f\"{path}/\"\n        return not is_valid_path(path, urlconf) and is_valid_path(slash_path, urlconf)\n\n    def fixup_language(self, lang):\n        return Language.objects.fuzzy_get(code=lang, strict=True)\n\n    def fixup_project(self, slug, request):\n        try:\n            project = Project.objects.get(slug__iexact=slug)\n        except Project.MultipleObjectsReturned:\n            return None\n        except Project.DoesNotExist:\n            try:\n                project = (\n                    Change.objects.filter(\n                        action=Change.ACTION_RENAME_PROJECT,\n                        old=slug,\n                    )\n                    .order()[0]\n                    .project\n                )\n            except IndexError:\n                return None\n\n        request.user.check_access(project)\n        return project\n\n    def fixup_component(self, slug, request, project):\n        try:\n            component = Component.objects.get(project=project, slug__iexact=slug)\n        except Component.DoesNotExist:\n            try:\n                component = (\n                    Change.objects.filter(\n                        action=Change.ACTION_RENAME_COMPONENT, old=slug\n                    )\n                    .order()[0]\n                    .component\n                )\n            except IndexError:\n                return None\n\n        request.user.check_access_component(component)\n        return component\n\n    def process_exception(self, request, exception):\n        if not isinstance(exception, Http404):\n            return None\n\n        try:\n            resolver_match = request.resolver_match\n        except AttributeError:\n            return None\n\n        kwargs = dict(resolver_match.kwargs)\n\n        if \"lang\" in kwargs:\n            language = self.fixup_language(kwargs[\"lang\"])\n            if language is None:\n                return None\n            kwargs[\"lang\"] = language.code\n\n        if \"project\" in kwargs:\n            project = self.fixup_project(kwargs[\"project\"], request)\n            if project is None:\n                return None\n            kwargs[\"project\"] = project.slug\n\n            if \"component\" in kwargs:\n                component = self.fixup_component(kwargs[\"component\"], request, project)\n                if component is None:\n                    return None\n                kwargs[\"component\"] = component.slug\n\n        if kwargs != resolver_match.kwargs:\n            query = request.META[\"QUERY_STRING\"]\n            if query:\n                query = f\"?{query}\"\n            return HttpResponsePermanentRedirect(\n                reverse(resolver_match.url_name, kwargs=kwargs) + query\n            )\n\n        return None\n\n\nclass SecurityMiddleware:\n    \"\"\"Middleware that sets Content-Security-Policy.\"\"\"\n\n    def __init__(self, get_response=None):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        response = self.get_response(request)\n        # No CSP for debug mode (to allow djdt or error pages)\n        if settings.DEBUG:\n            return response\n\n        style = {\"'self'\", \"'unsafe-inline'\"} | set(settings.CSP_STYLE_SRC)\n        script = {\"'self'\"} | set(settings.CSP_SCRIPT_SRC)\n        image = {\"'self'\"} | set(settings.CSP_IMG_SRC)\n        connect = {\"'self'\"} | set(settings.CSP_CONNECT_SRC)\n        font = {\"'self'\"} | set(settings.CSP_FONT_SRC)\n\n        if request.resolver_match and request.resolver_match.view_name in INLINE_PATHS:\n            script.add(\"'unsafe-inline'\")\n\n        # Support form\n        if request.resolver_match and request.resolver_match.view_name == \"manage\":\n            script.add(\"care.weblate.org\")\n            connect.add(\"care.weblate.org\")\n            style.add(\"care.weblate.org\")\n\n        # Rollbar client errors reporting\n        if (\n            hasattr(settings, \"ROLLBAR\")\n            and \"client_token\" in settings.ROLLBAR\n            and \"environment\" in settings.ROLLBAR\n            and response.status_code == 500\n        ):\n            script.add(\"'unsafe-inline'\")\n            script.add(\"cdnjs.cloudflare.com\")\n            connect.add(\"api.rollbar.com\")\n\n        # Sentry user feedback\n        if settings.SENTRY_DSN and response.status_code == 500:\n            domain = urlparse(settings.SENTRY_DSN).hostname\n            script.add(domain)\n            script.add(\"sentry.io\")\n            connect.add(domain)\n            connect.add(\"sentry.io\")\n            script.add(\"'unsafe-inline'\")\n            image.add(\"data:\")\n\n        # Matomo (Piwik) analytics\n        if settings.MATOMO_URL:\n            domain = urlparse(settings.MATOMO_URL).hostname\n            script.add(domain)\n            image.add(domain)\n            connect.add(domain)\n\n        # Google Analytics\n        if settings.GOOGLE_ANALYTICS_ID:\n            script.add(\"'unsafe-inline'\")\n            script.add(\"www.google-analytics.com\")\n            image.add(\"www.google-analytics.com\")\n\n        # External media URL\n        if \"://\" in settings.MEDIA_URL:\n            domain = urlparse(settings.MEDIA_URL).hostname\n            image.add(domain)\n\n        # External static URL\n        if \"://\" in settings.STATIC_URL:\n            domain = urlparse(settings.STATIC_URL).hostname\n            script.add(domain)\n            image.add(domain)\n            style.add(domain)\n            font.add(domain)\n\n        # CDN for fonts\n        if settings.FONTS_CDN_URL:\n            domain = urlparse(settings.FONTS_CDN_URL).hostname\n            style.add(domain)\n            font.add(domain)\n\n        # When using external image for Auth0 provider, add it here\n        if \"://\" in settings.SOCIAL_AUTH_AUTH0_IMAGE:\n            domain = urlparse(settings.SOCIAL_AUTH_AUTH0_IMAGE).hostname\n            image.add(domain)\n\n        response[\"Content-Security-Policy\"] = CSP_TEMPLATE.format(\n            \" \".join(style),\n            \" \".join(image),\n            \" \".join(script),\n            \" \".join(connect),\n            \" \".join(font),\n        )\n        if settings.SENTRY_SECURITY:\n            response[\"Content-Security-Policy\"] += \" report-uri {}\".format(\n                settings.SENTRY_SECURITY\n            )\n            response[\"Expect-CT\"] = 'max-age=86400, enforce, report-uri=\"{}\"'.format(\n                settings.SENTRY_SECURITY\n            )\n\n        # Opt-out from Google FLoC\n        response[\"Permissions-Policy\"] = \"interest-cohort=()\"\n\n        return response\n", "path": "weblate/middleware.py"}], "after_files": [{"content": "#\n# Copyright \u00a9 2012\u20132022 Michal \u010ciha\u0159 <michal@cihar.com>\n#\n# This file is part of Weblate <https://weblate.org/>\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <https://www.gnu.org/licenses/>.\n#\n\nfrom urllib.parse import urlparse\n\nfrom django.conf import settings\nfrom django.contrib import messages\nfrom django.core.exceptions import ValidationError\nfrom django.core.validators import validate_ipv46_address\nfrom django.http import Http404, HttpResponsePermanentRedirect\nfrom django.shortcuts import redirect\nfrom django.urls import is_valid_path, reverse\nfrom django.utils.http import escape_leading_slashes\nfrom django.utils.translation import gettext_lazy as _\n\nfrom weblate.lang.models import Language\nfrom weblate.trans.models import Change, Component, Project\nfrom weblate.utils.errors import report_error\nfrom weblate.utils.site import get_site_url\n\nCSP_TEMPLATE = (\n    \"default-src 'self'; style-src {0}; img-src {1}; script-src {2}; \"\n    \"connect-src {3}; object-src 'none'; font-src {4};\"\n    \"frame-src 'none'; frame-ancestors 'none';\"\n)\n\n# URLs requiring inline javascript\nINLINE_PATHS = {\"social:begin\", \"djangosaml2idp:saml_login_process\"}\n\n\nclass ProxyMiddleware:\n    \"\"\"Middleware that updates REMOTE_ADDR from proxy.\n\n    Note that this can have security implications and settings have to match your actual\n    proxy setup.\n    \"\"\"\n\n    def __init__(self, get_response=None):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        # Fake HttpRequest attribute to inject configured\n        # site name into build_absolute_uri\n        request._current_scheme_host = get_site_url()\n\n        # Actual proxy handling\n        proxy = None\n        if settings.IP_BEHIND_REVERSE_PROXY:\n            proxy = request.META.get(settings.IP_PROXY_HEADER)\n        if proxy:\n            # X_FORWARDED_FOR returns client1, proxy1, proxy2,...\n            address = proxy.split(\",\")[settings.IP_PROXY_OFFSET].strip()\n            try:\n                validate_ipv46_address(address)\n                request.META[\"REMOTE_ADDR\"] = address\n            except ValidationError:\n                report_error(cause=\"Invalid IP address\")\n\n        return self.get_response(request)\n\n\nclass RedirectMiddleware:\n    \"\"\"\n    Middleware that handles URL redirecting.\n\n    This used for fuzzy lookups of projects, for example case insensitive\n    or after renaming.\n    \"\"\"\n\n    def __init__(self, get_response=None):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        response = self.get_response(request)\n        # This is based on APPEND_SLASH handling in Django\n        if response.status_code == 404 and self.should_redirect_with_slash(request):\n            new_path = request.get_full_path(force_append_slash=True)\n            # Prevent construction of scheme relative urls.\n            new_path = escape_leading_slashes(new_path)\n            return HttpResponsePermanentRedirect(new_path)\n        return response\n\n    def should_redirect_with_slash(self, request):\n        path = request.path_info\n        # Avoid redirecting non GET requests, these would fail anyway due to\n        # missing parameters.\n        # Redirecting on API removes authentication headers in many cases,\n        # so avoid that as well.\n        if (\n            path.endswith(\"/\")\n            or request.method != \"GET\"\n            or path.startswith(f\"{settings.URL_PREFIX}/api\")\n        ):\n            return False\n        urlconf = getattr(request, \"urlconf\", None)\n        slash_path = f\"{path}/\"\n        return not is_valid_path(path, urlconf) and is_valid_path(slash_path, urlconf)\n\n    def fixup_language(self, lang):\n        return Language.objects.fuzzy_get(code=lang, strict=True)\n\n    def fixup_project(self, slug, request):\n        try:\n            project = Project.objects.get(slug__iexact=slug)\n        except Project.MultipleObjectsReturned:\n            return None\n        except Project.DoesNotExist:\n            try:\n                project = (\n                    Change.objects.filter(\n                        action=Change.ACTION_RENAME_PROJECT,\n                        old=slug,\n                    )\n                    .order()[0]\n                    .project\n                )\n            except IndexError:\n                return None\n\n        request.user.check_access(project)\n        return project\n\n    def fixup_component(self, slug, request, project):\n        try:\n            component = Component.objects.get(project=project, slug__iexact=slug)\n        except Component.DoesNotExist:\n            try:\n                component = (\n                    Change.objects.filter(\n                        action=Change.ACTION_RENAME_COMPONENT, old=slug\n                    )\n                    .order()[0]\n                    .component\n                )\n            except IndexError:\n                return None\n\n        request.user.check_access_component(component)\n        return component\n\n    def check_existing_translations(self, slug, project):\n        \"\"\"Check in existing translations for specific language.\n\n        Return False if language translation not present, else True.\n        \"\"\"\n        return any(lang.name == slug for lang in project.languages)\n\n    def process_exception(self, request, exception):\n        if not isinstance(exception, Http404):\n            return None\n\n        try:\n            resolver_match = request.resolver_match\n        except AttributeError:\n            return None\n\n        kwargs = dict(resolver_match.kwargs)\n        new_lang = None\n        if \"lang\" in kwargs:\n            language = self.fixup_language(kwargs[\"lang\"])\n            if language is None:\n                return None\n            kwargs[\"lang\"] = language.code\n            new_lang = language.name\n\n        if \"project\" in kwargs:\n            project = self.fixup_project(kwargs[\"project\"], request)\n            if project is None:\n                return None\n            kwargs[\"project\"] = project.slug\n\n            if \"component\" in kwargs:\n                component = self.fixup_component(kwargs[\"component\"], request, project)\n                if component is None:\n                    return None\n                kwargs[\"component\"] = component.slug\n\n                if new_lang:\n                    existing_trans = self.check_existing_translations(new_lang, project)\n                    if not existing_trans:\n                        messages.add_message(\n                            request,\n                            messages.INFO,\n                            _(\n                                f\"{new_lang} translation is currently not available,\\\n                                but can be added.\"\n                            ),\n                        )\n                        return redirect(\n                            reverse(\n                                \"component\",\n                                args=[kwargs[\"project\"], kwargs[\"component\"]],\n                            )\n                        )\n\n        if kwargs != resolver_match.kwargs:\n            query = request.META[\"QUERY_STRING\"]\n            if query:\n                query = f\"?{query}\"\n            return HttpResponsePermanentRedirect(\n                reverse(resolver_match.url_name, kwargs=kwargs) + query\n            )\n\n        return None\n\n\nclass SecurityMiddleware:\n    \"\"\"Middleware that sets Content-Security-Policy.\"\"\"\n\n    def __init__(self, get_response=None):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        response = self.get_response(request)\n        # No CSP for debug mode (to allow djdt or error pages)\n        if settings.DEBUG:\n            return response\n\n        style = {\"'self'\", \"'unsafe-inline'\"} | set(settings.CSP_STYLE_SRC)\n        script = {\"'self'\"} | set(settings.CSP_SCRIPT_SRC)\n        image = {\"'self'\"} | set(settings.CSP_IMG_SRC)\n        connect = {\"'self'\"} | set(settings.CSP_CONNECT_SRC)\n        font = {\"'self'\"} | set(settings.CSP_FONT_SRC)\n\n        if request.resolver_match and request.resolver_match.view_name in INLINE_PATHS:\n            script.add(\"'unsafe-inline'\")\n\n        # Support form\n        if request.resolver_match and request.resolver_match.view_name == \"manage\":\n            script.add(\"care.weblate.org\")\n            connect.add(\"care.weblate.org\")\n            style.add(\"care.weblate.org\")\n\n        # Rollbar client errors reporting\n        if (\n            hasattr(settings, \"ROLLBAR\")\n            and \"client_token\" in settings.ROLLBAR\n            and \"environment\" in settings.ROLLBAR\n            and response.status_code == 500\n        ):\n            script.add(\"'unsafe-inline'\")\n            script.add(\"cdnjs.cloudflare.com\")\n            connect.add(\"api.rollbar.com\")\n\n        # Sentry user feedback\n        if settings.SENTRY_DSN and response.status_code == 500:\n            domain = urlparse(settings.SENTRY_DSN).hostname\n            script.add(domain)\n            script.add(\"sentry.io\")\n            connect.add(domain)\n            connect.add(\"sentry.io\")\n            script.add(\"'unsafe-inline'\")\n            image.add(\"data:\")\n\n        # Matomo (Piwik) analytics\n        if settings.MATOMO_URL:\n            domain = urlparse(settings.MATOMO_URL).hostname\n            script.add(domain)\n            image.add(domain)\n            connect.add(domain)\n\n        # Google Analytics\n        if settings.GOOGLE_ANALYTICS_ID:\n            script.add(\"'unsafe-inline'\")\n            script.add(\"www.google-analytics.com\")\n            image.add(\"www.google-analytics.com\")\n\n        # External media URL\n        if \"://\" in settings.MEDIA_URL:\n            domain = urlparse(settings.MEDIA_URL).hostname\n            image.add(domain)\n\n        # External static URL\n        if \"://\" in settings.STATIC_URL:\n            domain = urlparse(settings.STATIC_URL).hostname\n            script.add(domain)\n            image.add(domain)\n            style.add(domain)\n            font.add(domain)\n\n        # CDN for fonts\n        if settings.FONTS_CDN_URL:\n            domain = urlparse(settings.FONTS_CDN_URL).hostname\n            style.add(domain)\n            font.add(domain)\n\n        # When using external image for Auth0 provider, add it here\n        if \"://\" in settings.SOCIAL_AUTH_AUTH0_IMAGE:\n            domain = urlparse(settings.SOCIAL_AUTH_AUTH0_IMAGE).hostname\n            image.add(domain)\n\n        response[\"Content-Security-Policy\"] = CSP_TEMPLATE.format(\n            \" \".join(style),\n            \" \".join(image),\n            \" \".join(script),\n            \" \".join(connect),\n            \" \".join(font),\n        )\n        if settings.SENTRY_SECURITY:\n            response[\"Content-Security-Policy\"] += \" report-uri {}\".format(\n                settings.SENTRY_SECURITY\n            )\n            response[\"Expect-CT\"] = 'max-age=86400, enforce, report-uri=\"{}\"'.format(\n                settings.SENTRY_SECURITY\n            )\n\n        # Opt-out from Google FLoC\n        response[\"Permissions-Policy\"] = \"interest-cohort=()\"\n\n        return response\n", "path": "weblate/middleware.py"}]}