summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorar3ph <192461522+ar3ph@users.noreply.github.com>2026-04-27 21:20:49 +0000
committerJacob Walls <jacobtylerwalls@gmail.com>2026-06-09 11:52:09 -0400
commit46c5e76f0bcc76bfce19ad7ba07f716fc653a822 (patch)
treef13bbad8e285cf245f2fa4044f5cb8c311b78b27
parentbeb40ed1d2b0503814591a37b08ecbd71f2ff729 (diff)
Fixed #36900 -- Used safe_join() on downloaded template archive.
-rw-r--r--django/core/management/templates.py3
-rw-r--r--tests/admin_scripts/tests.py15
-rw-r--r--tests/admin_scripts/urls.py6
-rw-r--r--tests/admin_scripts/views.py17
4 files changed, 40 insertions, 1 deletions
diff --git a/django/core/management/templates.py b/django/core/management/templates.py
index ea2c4a294f..1751f12cbc 100644
--- a/django/core/management/templates.py
+++ b/django/core/management/templates.py
@@ -18,6 +18,7 @@ from django.core.management.utils import (
)
from django.template import Context, Engine
from django.utils import archive
+from django.utils._os import safe_join
from django.utils.http import parse_header_parameters
from django.utils.version import get_docs_version
@@ -345,7 +346,7 @@ class TemplateCommand(BaseCommand):
# Move the temporary file to a filename that has better
# chances of being recognized by the archive utils
if used_name != guessed_filename:
- guessed_path = os.path.join(tempdir, guessed_filename)
+ guessed_path = safe_join(tempdir, guessed_filename)
shutil.move(the_path, guessed_path)
return guessed_path
diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py
index 819ba931d6..3eb7b97c99 100644
--- a/tests/admin_scripts/tests.py
+++ b/tests/admin_scripts/tests.py
@@ -2778,6 +2778,21 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
self.assertTrue(os.path.isdir(testproject_dir))
self.assertTrue(os.path.exists(os.path.join(testproject_dir, "run.py")))
+ def test_custom_project_template_from_tarball_by_url_bad_filename(self):
+ """
+ The startproject management command will raise SuspiciousFileOperation
+ on an ill-formed remote template archive filename.
+ """
+ template_url = "%s/bad_template_filename.tgz" % self.live_server_url
+
+ args = ["startproject", "--template", template_url, "urltestproject"]
+
+ out, err = self.run_django_admin(args)
+ self.assertOutput(
+ err,
+ "is located outside of the base path component",
+ )
+
def test_custom_project_template_from_tarball_by_url_django_user_agent(self):
user_agent = None
diff --git a/tests/admin_scripts/urls.py b/tests/admin_scripts/urls.py
index a6cc7fe1b5..3e7aa063d3 100644
--- a/tests/admin_scripts/urls.py
+++ b/tests/admin_scripts/urls.py
@@ -3,6 +3,8 @@ import os
from django.urls import path
from django.views.static import serve
+from . import views
+
here = os.path.dirname(__file__)
urlpatterns = [
@@ -11,4 +13,8 @@ urlpatterns = [
serve,
{"document_root": os.path.join(here, "custom_templates")},
),
+ path(
+ "bad_template_filename.tgz",
+ views.template_bad_filename,
+ ),
]
diff --git a/tests/admin_scripts/views.py b/tests/admin_scripts/views.py
new file mode 100644
index 0000000000..d57803659d
--- /dev/null
+++ b/tests/admin_scripts/views.py
@@ -0,0 +1,17 @@
+from pathlib import Path
+
+from django.http import FileResponse
+
+
+def template_bad_filename(request):
+ content = Path(__file__).parent / "custom_templates" / "project_template.tgz"
+ f = open(content, "rb")
+ filename = "/nonexistent/archive.tgz"
+ response = FileResponse(
+ f,
+ as_attachment=True,
+ filename=filename,
+ )
+ # Force the filename to have a slash at the beginning.
+ response["Content-Disposition"] = f'attachment; filename="{filename}"'
+ return response