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
**/vendor/**/*.js
django/contrib/gis/templates/**/*.js
django/views/templates/*.js
docs/_build/**/*.js
node_modules/**.js
tests/**/*.js

View File

@ -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,95 +14,19 @@ 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/">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"
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):
"""
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)
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.

View File

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

View File

@ -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 = {}

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.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")

View File

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

View File

@ -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):
"""