summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Pope <nick.pope@flightdataservices.com>2019-04-07 21:01:47 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-02-17 09:50:50 +0100
commit8eef22dfed2d53df0da10c0090d9cb04f66efb15 (patch)
treeb6c8154c346f456bb0b68eeb40746cc8ab2044bd
parentbae053d497ba8a8de7e4f725973924bfb1885fd2 (diff)
Fixed #34343 -- Moved built-in templates to filesystem.
-rw-r--r--.eslintignore1
-rw-r--r--django/views/csrf.py99
-rw-r--r--django/views/i18n.py120
-rw-r--r--django/views/static.py41
-rw-r--r--django/views/templates/csrf_403.html84
-rw-r--r--django/views/templates/directory_index.html21
-rw-r--r--django/views/templates/i18n_catalog.js102
-rw-r--r--tests/view_tests/tests/test_csrf.py14
-rw-r--r--tests/view_tests/tests/test_i18n.py15
-rw-r--r--tests/view_tests/tests/test_static.py15
10 files changed, 292 insertions, 220 deletions
diff --git a/.eslintignore b/.eslintignore
index 9c273ed532..6e4edbd66d 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,6 +1,7 @@
**/*.min.js
**/vendor/**/*.js
django/contrib/gis/templates/**/*.js
+django/views/templates/*.js
docs/_build/**/*.js
node_modules/**.js
tests/**/*.js
diff --git a/django/views/csrf.py b/django/views/csrf.py
index 53ca2cb823..1b34adbe4b 100644
--- a/django/views/csrf.py
+++ b/django/views/csrf.py
@@ -1,3 +1,5 @@
+from pathlib import Path
+
from django.conf import settings
from django.http import HttpResponseForbidden
from django.template import Context, Engine, TemplateDoesNotExist, loader
@@ -12,93 +14,17 @@ from django.utils.version import get_docs_version
# tags cannot be used with this inline templates as makemessages would not be
# able to discover the strings.
-CSRF_FAILURE_TEMPLATE = """
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta http-equiv="content-type" content="text/html; charset=utf-8">
- <meta name="robots" content="NONE,NOARCHIVE">
- <title>403 Forbidden</title>
- <style type="text/css">
- html * { padding:0; margin:0; }
- body * { padding:10px 20px; }
- body * * { padding:0; }
- body { font:small sans-serif; background:#eee; color:#000; }
- body>div { border-bottom:1px solid #ddd; }
- h1 { font-weight:normal; margin-bottom:.4em; }
- h1 span { font-size:60%; color:#666; font-weight:normal; }
- #info { background:#f6f6f6; }
- #info ul { margin: 0.5em 4em; }
- #info p, #summary p { padding-top:10px; }
- #summary { background: #ffc; }
- #explanation { background:#eee; border-bottom: 0px none; }
- </style>
-</head>
-<body>
-<div id="summary">
- <h1>{{ title }} <span>(403)</span></h1>
- <p>{{ main }}</p>
-{% if no_referer %}
- <p>{{ no_referer1 }}</p>
- <p>{{ no_referer2 }}</p>
- <p>{{ no_referer3 }}</p>
-{% endif %}
-{% if no_cookie %}
- <p>{{ no_cookie1 }}</p>
- <p>{{ no_cookie2 }}</p>
-{% endif %}
-</div>
-{% if DEBUG %}
-<div id="info">
- <h2>Help</h2>
- {% if reason %}
- <p>Reason given for failure:</p>
- <pre>
- {{ reason }}
- </pre>
- {% endif %}
-
- <p>In general, this can occur when there is a genuine Cross Site Request Forgery, or when
- <a
- href="https://docs.djangoproject.com/en/{{ docs_version }}/ref/csrf/">Django’s
- CSRF mechanism</a> has not been used correctly. For POST forms, you need to
- ensure:</p>
-
- <ul>
- <li>Your browser is accepting cookies.</li>
-
- <li>The view function passes a <code>request</code> to the template’s <a
- href="https://docs.djangoproject.com/en/dev/topics/templates/#django.template.backends.base.Template.render"><code>render</code></a>
- method.</li>
-
- <li>In the template, there is a <code>{% templatetag openblock %} csrf_token
- {% templatetag closeblock %}</code> template tag inside each POST form that
- targets an internal URL.</li>
-
- <li>If you are not using <code>CsrfViewMiddleware</code>, then you must use
- <code>csrf_protect</code> on any views that use the <code>csrf_token</code>
- template tag, as well as those that accept the POST data.</li>
+CSRF_FAILURE_TEMPLATE_NAME = "403_csrf.html"
- <li>The form has a valid CSRF token. After logging in in another browser
- tab or hitting the back button after a login, you may need to reload the
- page with the form, because the token is rotated after a login.</li>
- </ul>
- <p>You’re seeing the help section of this page because you have <code>DEBUG =
- True</code> in your Django settings file. Change that to <code>False</code>,
- and only the initial error message will be displayed. </p>
+def builtin_template_path(name):
+ """
+ Return a path to a builtin template.
- <p>You can customize this page using the CSRF_FAILURE_VIEW setting.</p>
-</div>
-{% else %}
-<div id="explanation">
- <p><small>{{ more }}</small></p>
-</div>
-{% endif %}
-</body>
-</html>
-""" # NOQA
-CSRF_FAILURE_TEMPLATE_NAME = "403_csrf.html"
+ Avoid calling this function at the module level or in a class-definition
+ because __file__ may not exist, e.g. in frozen environments.
+ """
+ return Path(__file__).parent / "templates" / name
def csrf_failure(request, reason="", template_name=CSRF_FAILURE_TEMPLATE_NAME):
@@ -151,8 +77,9 @@ def csrf_failure(request, reason="", template_name=CSRF_FAILURE_TEMPLATE_NAME):
t = loader.get_template(template_name)
except TemplateDoesNotExist:
if template_name == CSRF_FAILURE_TEMPLATE_NAME:
- # If the default template doesn't exist, use the string template.
- t = Engine().from_string(CSRF_FAILURE_TEMPLATE)
+ # If the default template doesn't exist, use the fallback template.
+ with builtin_template_path("csrf_403.html").open(encoding="utf-8") as fh:
+ t = Engine().from_string(fh.read())
c = Context(c)
else:
# Raise if a developer-specified template doesn't exist.
diff --git a/django/views/i18n.py b/django/views/i18n.py
index 91f797dce9..771035d8ab 100644
--- a/django/views/i18n.py
+++ b/django/views/i18n.py
@@ -1,6 +1,7 @@
import json
import os
import re
+from pathlib import Path
from django.apps import apps
from django.conf import settings
@@ -16,6 +17,16 @@ from django.views.generic import View
LANGUAGE_QUERY_PARAMETER = "language"
+def builtin_template_path(name):
+ """
+ Return a path to a builtin template.
+
+ Avoid calling this function at the module level or in a class-definition
+ because __file__ may not exist, e.g. in frozen environments.
+ """
+ return Path(__file__).parent / "templates" / name
+
+
def set_language(request):
"""
Redirect to a given URL while setting the chosen language in the session
@@ -84,112 +95,6 @@ def get_formats():
return {attr: get_format(attr) for attr in FORMAT_SETTINGS}
-js_catalog_template = r"""
-{% autoescape off %}
-'use strict';
-{
- const globals = this;
- const django = globals.django || (globals.django = {});
-
- {% if plural %}
- django.pluralidx = function(n) {
- const v = {{ plural }};
- if (typeof v === 'boolean') {
- return v ? 1 : 0;
- } else {
- return v;
- }
- };
- {% else %}
- django.pluralidx = function(count) { return (count == 1) ? 0 : 1; };
- {% endif %}
-
- /* gettext library */
-
- django.catalog = django.catalog || {};
- {% if catalog_str %}
- const newcatalog = {{ catalog_str }};
- for (const key in newcatalog) {
- django.catalog[key] = newcatalog[key];
- }
- {% endif %}
-
- if (!django.jsi18n_initialized) {
- django.gettext = function(msgid) {
- const value = django.catalog[msgid];
- if (typeof value === 'undefined') {
- return msgid;
- } else {
- return (typeof value === 'string') ? value : value[0];
- }
- };
-
- django.ngettext = function(singular, plural, count) {
- const value = django.catalog[singular];
- if (typeof value === 'undefined') {
- return (count == 1) ? singular : plural;
- } else {
- return value.constructor === Array ? value[django.pluralidx(count)] : value;
- }
- };
-
- django.gettext_noop = function(msgid) { return msgid; };
-
- django.pgettext = function(context, msgid) {
- let value = django.gettext(context + '\x04' + msgid);
- if (value.includes('\x04')) {
- value = msgid;
- }
- return value;
- };
-
- django.npgettext = function(context, singular, plural, count) {
- let value = django.ngettext(context + '\x04' + singular, context + '\x04' + plural, count);
- if (value.includes('\x04')) {
- value = django.ngettext(singular, plural, count);
- }
- return value;
- };
-
- django.interpolate = function(fmt, obj, named) {
- if (named) {
- return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
- } else {
- return fmt.replace(/%s/g, function(match){return String(obj.shift())});
- }
- };
-
-
- /* formatting library */
-
- django.formats = {{ formats_str }};
-
- django.get_format = function(format_type) {
- const value = django.formats[format_type];
- if (typeof value === 'undefined') {
- return format_type;
- } else {
- return value;
- }
- };
-
- /* add to global namespace */
- globals.pluralidx = django.pluralidx;
- globals.gettext = django.gettext;
- globals.ngettext = django.ngettext;
- globals.gettext_noop = django.gettext_noop;
- globals.pgettext = django.pgettext;
- globals.npgettext = django.npgettext;
- globals.interpolate = django.interpolate;
- globals.get_format = django.get_format;
-
- django.jsi18n_initialized = true;
- }
-};
-{% endautoescape %}
-""" # NOQA
-
-
class JavaScriptCatalog(View):
"""
Return the selected language catalog as a JavaScript library.
@@ -308,7 +213,8 @@ class JavaScriptCatalog(View):
def indent(s):
return s.replace("\n", "\n ")
- template = Engine().from_string(js_catalog_template)
+ with builtin_template_path("i18n_catalog.js").open(encoding="utf-8") as fh:
+ template = Engine().from_string(fh.read())
context["catalog_str"] = (
indent(json.dumps(context["catalog"], sort_keys=True, indent=2))
if context["catalog"]
diff --git a/django/views/static.py b/django/views/static.py
index f75b86f970..df46c53093 100644
--- a/django/views/static.py
+++ b/django/views/static.py
@@ -14,6 +14,16 @@ from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy
+def builtin_template_path(name):
+ """
+ Return a path to a builtin template.
+
+ Avoid calling this function at the module level or in a class-definition
+ because __file__ may not exist, e.g. in frozen environments.
+ """
+ return Path(__file__).parent / "templates" / name
+
+
def serve(request, path, document_root=None, show_indexes=False):
"""
Serve static files below a given point in the directory structure.
@@ -53,29 +63,7 @@ def serve(request, path, document_root=None, show_indexes=False):
return response
-DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
-{% load i18n %}
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta http-equiv="Content-type" content="text/html; charset=utf-8">
- <meta http-equiv="Content-Language" content="en-us">
- <meta name="robots" content="NONE,NOARCHIVE">
- <title>{% blocktranslate %}Index of {{ directory }}{% endblocktranslate %}</title>
- </head>
- <body>
- <h1>{% blocktranslate %}Index of {{ directory }}{% endblocktranslate %}</h1>
- <ul>
- {% if directory != "/" %}
- <li><a href="../">../</a></li>
- {% endif %}
- {% for f in file_list %}
- <li><a href="{{ f|urlencode }}">{{ f }}</a></li>
- {% endfor %}
- </ul>
- </body>
-</html>
-"""
+# Translatable string for static directory index template title.
template_translatable = gettext_lazy("Index of %(directory)s")
@@ -88,9 +76,10 @@ def directory_index(path, fullpath):
]
)
except TemplateDoesNotExist:
- t = Engine(libraries={"i18n": "django.templatetags.i18n"}).from_string(
- DEFAULT_DIRECTORY_INDEX_TEMPLATE
- )
+ with builtin_template_path("directory_index.html").open(encoding="utf-8") as fh:
+ t = Engine(libraries={"i18n": "django.templatetags.i18n"}).from_string(
+ fh.read()
+ )
c = Context()
else:
c = {}
diff --git a/django/views/templates/csrf_403.html b/django/views/templates/csrf_403.html
new file mode 100644
index 0000000000..402a2c6cdd
--- /dev/null
+++ b/django/views/templates/csrf_403.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <meta name="robots" content="NONE,NOARCHIVE">
+ <title>403 Forbidden</title>
+ <style type="text/css">
+ html * { padding:0; margin:0; }
+ body * { padding:10px 20px; }
+ body * * { padding:0; }
+ body { font:small sans-serif; background:#eee; color:#000; }
+ body>div { border-bottom:1px solid #ddd; }
+ h1 { font-weight:normal; margin-bottom:.4em; }
+ h1 span { font-size:60%; color:#666; font-weight:normal; }
+ #info { background:#f6f6f6; }
+ #info ul { margin: 0.5em 4em; }
+ #info p, #summary p { padding-top:10px; }
+ #summary { background: #ffc; }
+ #explanation { background:#eee; border-bottom: 0px none; }
+ </style>
+</head>
+<body>
+<div id="summary">
+ <h1>{{ title }} <span>(403)</span></h1>
+ <p>{{ main }}</p>
+{% if no_referer %}
+ <p>{{ no_referer1 }}</p>
+ <p>{{ no_referer2 }}</p>
+ <p>{{ no_referer3 }}</p>
+{% endif %}
+{% if no_cookie %}
+ <p>{{ no_cookie1 }}</p>
+ <p>{{ no_cookie2 }}</p>
+{% endif %}
+</div>
+{% if DEBUG %}
+<div id="info">
+ <h2>Help</h2>
+ {% if reason %}
+ <p>Reason given for failure:</p>
+ <pre>
+ {{ reason }}
+ </pre>
+ {% endif %}
+
+ <p>In general, this can occur when there is a genuine Cross Site Request Forgery, or when
+ <a
+ href="https://docs.djangoproject.com/en/{{ docs_version }}/ref/csrf/">Django’s
+ CSRF mechanism</a> has not been used correctly. For POST forms, you need to
+ ensure:</p>
+
+ <ul>
+ <li>Your browser is accepting cookies.</li>
+
+ <li>The view function passes a <code>request</code> to the template’s <a
+ href="https://docs.djangoproject.com/en/dev/topics/templates/#django.template.backends.base.Template.render"><code>render</code></a>
+ method.</li>
+
+ <li>In the template, there is a <code>{% templatetag openblock %} csrf_token
+ {% templatetag closeblock %}</code> template tag inside each POST form that
+ targets an internal URL.</li>
+
+ <li>If you are not using <code>CsrfViewMiddleware</code>, then you must use
+ <code>csrf_protect</code> on any views that use the <code>csrf_token</code>
+ template tag, as well as those that accept the POST data.</li>
+
+ <li>The form has a valid CSRF token. After logging in in another browser
+ tab or hitting the back button after a login, you may need to reload the
+ page with the form, because the token is rotated after a login.</li>
+ </ul>
+
+ <p>You’re seeing the help section of this page because you have <code>DEBUG =
+ True</code> in your Django settings file. Change that to <code>False</code>,
+ and only the initial error message will be displayed. </p>
+
+ <p>You can customize this page using the CSRF_FAILURE_VIEW setting.</p>
+</div>
+{% else %}
+<div id="explanation">
+ <p><small>{{ more }}</small></p>
+</div>
+{% endif %}
+</body>
+</html>
diff --git a/django/views/templates/directory_index.html b/django/views/templates/directory_index.html
new file mode 100644
index 0000000000..d67e5e0edd
--- /dev/null
+++ b/django/views/templates/directory_index.html
@@ -0,0 +1,21 @@
+{% load i18n %}
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+ <meta http-equiv="Content-Language" content="en-us">
+ <meta name="robots" content="NONE,NOARCHIVE">
+ <title>{% blocktranslate %}Index of {{ directory }}{% endblocktranslate %}</title>
+ </head>
+ <body>
+ <h1>{% blocktranslate %}Index of {{ directory }}{% endblocktranslate %}</h1>
+ <ul>
+ {% if directory != "/" %}
+ <li><a href="../">../</a></li>
+ {% endif %}
+ {% for f in file_list %}
+ <li><a href="{{ f|urlencode }}">{{ f }}</a></li>
+ {% endfor %}
+ </ul>
+ </body>
+</html>
diff --git a/django/views/templates/i18n_catalog.js b/django/views/templates/i18n_catalog.js
new file mode 100644
index 0000000000..b1fe4a9aac
--- /dev/null
+++ b/django/views/templates/i18n_catalog.js
@@ -0,0 +1,102 @@
+{% autoescape off %}
+'use strict';
+{
+ const globals = this;
+ const django = globals.django || (globals.django = {});
+
+ {% if plural %}
+ django.pluralidx = function(n) {
+ const v = {{ plural }};
+ if (typeof v === 'boolean') {
+ return v ? 1 : 0;
+ } else {
+ return v;
+ }
+ };
+ {% else %}
+ django.pluralidx = function(count) { return (count == 1) ? 0 : 1; };
+ {% endif %}
+
+ /* gettext library */
+
+ django.catalog = django.catalog || {};
+ {% if catalog_str %}
+ const newcatalog = {{ catalog_str }};
+ for (const key in newcatalog) {
+ django.catalog[key] = newcatalog[key];
+ }
+ {% endif %}
+
+ if (!django.jsi18n_initialized) {
+ django.gettext = function(msgid) {
+ const value = django.catalog[msgid];
+ if (typeof value === 'undefined') {
+ return msgid;
+ } else {
+ return (typeof value === 'string') ? value : value[0];
+ }
+ };
+
+ django.ngettext = function(singular, plural, count) {
+ const value = django.catalog[singular];
+ if (typeof value === 'undefined') {
+ return (count == 1) ? singular : plural;
+ } else {
+ return value.constructor === Array ? value[django.pluralidx(count)] : value;
+ }
+ };
+
+ django.gettext_noop = function(msgid) { return msgid; };
+
+ django.pgettext = function(context, msgid) {
+ let value = django.gettext(context + '\x04' + msgid);
+ if (value.includes('\x04')) {
+ value = msgid;
+ }
+ return value;
+ };
+
+ django.npgettext = function(context, singular, plural, count) {
+ let value = django.ngettext(context + '\x04' + singular, context + '\x04' + plural, count);
+ if (value.includes('\x04')) {
+ value = django.ngettext(singular, plural, count);
+ }
+ return value;
+ };
+
+ django.interpolate = function(fmt, obj, named) {
+ if (named) {
+ return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
+ } else {
+ return fmt.replace(/%s/g, function(match){return String(obj.shift())});
+ }
+ };
+
+
+ /* formatting library */
+
+ django.formats = {{ formats_str }};
+
+ django.get_format = function(format_type) {
+ const value = django.formats[format_type];
+ if (typeof value === 'undefined') {
+ return format_type;
+ } else {
+ return value;
+ }
+ };
+
+ /* add to global namespace */
+ globals.pluralidx = django.pluralidx;
+ globals.gettext = django.gettext;
+ globals.ngettext = django.ngettext;
+ globals.gettext_noop = django.gettext_noop;
+ globals.pgettext = django.pgettext;
+ globals.npgettext = django.npgettext;
+ globals.interpolate = django.interpolate;
+ globals.get_format = django.get_format;
+
+ django.jsi18n_initialized = true;
+ }
+};
+{% endautoescape %}
diff --git a/tests/view_tests/tests/test_csrf.py b/tests/view_tests/tests/test_csrf.py
index 68198672ed..ef4a50dd45 100644
--- a/tests/view_tests/tests/test_csrf.py
+++ b/tests/view_tests/tests/test_csrf.py
@@ -1,3 +1,5 @@
+from unittest import mock
+
from django.template import TemplateDoesNotExist
from django.test import Client, RequestFactory, SimpleTestCase, override_settings
from django.utils.translation import override
@@ -117,3 +119,15 @@ class CsrfViewTests(SimpleTestCase):
request = factory.post("/")
with self.assertRaises(TemplateDoesNotExist):
csrf_failure(request, template_name="nonexistent.html")
+
+ def test_template_encoding(self):
+ """
+ The template is loaded directly, not via a template loader, and should
+ be opened as utf-8 charset as is the default specified on template
+ engines.
+ """
+ from django.views.csrf import Path
+
+ with mock.patch.object(Path, "open") as m:
+ csrf_failure(mock.MagicMock(), mock.Mock())
+ m.assert_called_once_with(encoding="utf-8")
diff --git a/tests/view_tests/tests/test_i18n.py b/tests/view_tests/tests/test_i18n.py
index e9f3e984b5..93e91bcc83 100644
--- a/tests/view_tests/tests/test_i18n.py
+++ b/tests/view_tests/tests/test_i18n.py
@@ -1,6 +1,7 @@
import gettext
import json
from os import path
+from unittest import mock
from django.conf import settings
from django.test import (
@@ -507,6 +508,20 @@ class I18NViewTests(SimpleTestCase):
with self.assertRaisesMessage(ValueError, msg):
view(request, packages="unknown_package+unknown_package2")
+ def test_template_encoding(self):
+ """
+ The template is loaded directly, not via a template loader, and should
+ be opened as utf-8 charset as is the default specified on template
+ engines.
+ """
+ from django.views.i18n import Path
+
+ view = JavaScriptCatalog.as_view()
+ request = RequestFactory().get("/")
+ with mock.patch.object(Path, "open") as m:
+ view(request)
+ m.assert_called_once_with(encoding="utf-8")
+
@override_settings(ROOT_URLCONF="view_tests.urls")
class I18nSeleniumTests(SeleniumTestCase):
diff --git a/tests/view_tests/tests/test_static.py b/tests/view_tests/tests/test_static.py
index 309b81f8fa..3fa382749b 100644
--- a/tests/view_tests/tests/test_static.py
+++ b/tests/view_tests/tests/test_static.py
@@ -1,6 +1,7 @@
import mimetypes
import unittest
from os import path
+from unittest import mock
from urllib.parse import quote
from django.conf.urls.static import static
@@ -8,7 +9,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.http import FileResponse, HttpResponseNotModified
from django.test import SimpleTestCase, override_settings
from django.utils.http import http_date
-from django.views.static import was_modified_since
+from django.views.static import directory_index, was_modified_since
from .. import urls
from ..urls import media_dir
@@ -152,6 +153,18 @@ class StaticTests(SimpleTestCase):
response = self.client.get("/%s/" % self.prefix)
self.assertEqual(response.content, b"Test index")
+ def test_template_encoding(self):
+ """
+ The template is loaded directly, not via a template loader, and should
+ be opened as utf-8 charset as is the default specified on template
+ engines.
+ """
+ from django.views.static import Path
+
+ with mock.patch.object(Path, "open") as m:
+ directory_index(mock.MagicMock(), mock.MagicMock())
+ m.assert_called_once_with(encoding="utf-8")
+
class StaticHelperTest(StaticTests):
"""