Fixed #34343 -- Moved built-in templates to filesystem.

This commit is contained in:
Nick Pope 2019-04-07 21:01:47 +01:00 committed by Mariusz Felisiak
parent bae053d497
commit 8eef22dfed
10 changed files with 294 additions and 222 deletions

View File

@ -1,6 +1,7 @@
**/*.min.js **/*.min.js
**/vendor/**/*.js **/vendor/**/*.js
django/contrib/gis/templates/**/*.js django/contrib/gis/templates/**/*.js
django/views/templates/*.js
docs/_build/**/*.js docs/_build/**/*.js
node_modules/**.js node_modules/**.js
tests/**/*.js tests/**/*.js

View File

@ -1,3 +1,5 @@
from pathlib import Path
from django.conf import settings from django.conf import settings
from django.http import HttpResponseForbidden from django.http import HttpResponseForbidden
from django.template import Context, Engine, TemplateDoesNotExist, loader from django.template import Context, Engine, TemplateDoesNotExist, loader
@ -12,95 +14,19 @@ from django.utils.version import get_docs_version
# tags cannot be used with this inline templates as makemessages would not be # tags cannot be used with this inline templates as makemessages would not be
# able to discover the strings. # 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/">Djangos
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 templates <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>Youre 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>
""" # NOQA
CSRF_FAILURE_TEMPLATE_NAME = "403_csrf.html" CSRF_FAILURE_TEMPLATE_NAME = "403_csrf.html"
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 csrf_failure(request, reason="", template_name=CSRF_FAILURE_TEMPLATE_NAME): def csrf_failure(request, reason="", template_name=CSRF_FAILURE_TEMPLATE_NAME):
""" """
Default view used when request fails CSRF protection Default view used when request fails CSRF protection
@ -151,8 +77,9 @@ def csrf_failure(request, reason="", template_name=CSRF_FAILURE_TEMPLATE_NAME):
t = loader.get_template(template_name) t = loader.get_template(template_name)
except TemplateDoesNotExist: except TemplateDoesNotExist:
if template_name == CSRF_FAILURE_TEMPLATE_NAME: if template_name == CSRF_FAILURE_TEMPLATE_NAME:
# If the default template doesn't exist, use the string template. # If the default template doesn't exist, use the fallback template.
t = Engine().from_string(CSRF_FAILURE_TEMPLATE) with builtin_template_path("csrf_403.html").open(encoding="utf-8") as fh:
t = Engine().from_string(fh.read())
c = Context(c) c = Context(c)
else: else:
# Raise if a developer-specified template doesn't exist. # Raise if a developer-specified template doesn't exist.

View File

@ -1,6 +1,7 @@
import json import json
import os import os
import re import re
from pathlib import Path
from django.apps import apps from django.apps import apps
from django.conf import settings from django.conf import settings
@ -16,6 +17,16 @@ from django.views.generic import View
LANGUAGE_QUERY_PARAMETER = "language" 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): def set_language(request):
""" """
Redirect to a given URL while setting the chosen language in the session 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} 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): class JavaScriptCatalog(View):
""" """
Return the selected language catalog as a JavaScript library. Return the selected language catalog as a JavaScript library.
@ -308,7 +213,8 @@ class JavaScriptCatalog(View):
def indent(s): def indent(s):
return s.replace("\n", "\n ") 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"] = ( context["catalog_str"] = (
indent(json.dumps(context["catalog"], sort_keys=True, indent=2)) indent(json.dumps(context["catalog"], sort_keys=True, indent=2))
if context["catalog"] if context["catalog"]

View File

@ -14,6 +14,16 @@ from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy 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): def serve(request, path, document_root=None, show_indexes=False):
""" """
Serve static files below a given point in the directory structure. 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 return response
DEFAULT_DIRECTORY_INDEX_TEMPLATE = """ # Translatable string for static directory index template title.
{% 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>
"""
template_translatable = gettext_lazy("Index of %(directory)s") template_translatable = gettext_lazy("Index of %(directory)s")
@ -88,8 +76,9 @@ def directory_index(path, fullpath):
] ]
) )
except TemplateDoesNotExist: except TemplateDoesNotExist:
with builtin_template_path("directory_index.html").open(encoding="utf-8") as fh:
t = Engine(libraries={"i18n": "django.templatetags.i18n"}).from_string( t = Engine(libraries={"i18n": "django.templatetags.i18n"}).from_string(
DEFAULT_DIRECTORY_INDEX_TEMPLATE fh.read()
) )
c = Context() c = Context()
else: else:

View File

@ -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/">Djangos
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 templates <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>Youre 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>

View File

@ -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>

View File

@ -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 %}

View File

@ -1,3 +1,5 @@
from unittest import mock
from django.template import TemplateDoesNotExist from django.template import TemplateDoesNotExist
from django.test import Client, RequestFactory, SimpleTestCase, override_settings from django.test import Client, RequestFactory, SimpleTestCase, override_settings
from django.utils.translation import override from django.utils.translation import override
@ -117,3 +119,15 @@ class CsrfViewTests(SimpleTestCase):
request = factory.post("/") request = factory.post("/")
with self.assertRaises(TemplateDoesNotExist): with self.assertRaises(TemplateDoesNotExist):
csrf_failure(request, template_name="nonexistent.html") 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")

View File

@ -1,6 +1,7 @@
import gettext import gettext
import json import json
from os import path from os import path
from unittest import mock
from django.conf import settings from django.conf import settings
from django.test import ( from django.test import (
@ -507,6 +508,20 @@ class I18NViewTests(SimpleTestCase):
with self.assertRaisesMessage(ValueError, msg): with self.assertRaisesMessage(ValueError, msg):
view(request, packages="unknown_package+unknown_package2") 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") @override_settings(ROOT_URLCONF="view_tests.urls")
class I18nSeleniumTests(SeleniumTestCase): class I18nSeleniumTests(SeleniumTestCase):

View File

@ -1,6 +1,7 @@
import mimetypes import mimetypes
import unittest import unittest
from os import path from os import path
from unittest import mock
from urllib.parse import quote from urllib.parse import quote
from django.conf.urls.static import static 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.http import FileResponse, HttpResponseNotModified
from django.test import SimpleTestCase, override_settings from django.test import SimpleTestCase, override_settings
from django.utils.http import http_date 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 .. import urls
from ..urls import media_dir from ..urls import media_dir
@ -152,6 +153,18 @@ class StaticTests(SimpleTestCase):
response = self.client.get("/%s/" % self.prefix) response = self.client.get("/%s/" % self.prefix)
self.assertEqual(response.content, b"Test index") 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): class StaticHelperTest(StaticTests):
""" """