Skip to content

Commit 36be97b

Browse files
blighjn6g7
authored andcommitted
Fixed django#21080 -- Ignored urls inside comments during collectstatic.
Thanks Mariusz Felisiak for the review. Co-authored-by: Nathan Gaberel <nathan@gnab.fr>
1 parent 35dab0a commit 36be97b

5 files changed

Lines changed: 93 additions & 11 deletions

File tree

django/contrib/staticfiles/storage.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@
1111
from django.core.files.base import ContentFile
1212
from django.core.files.storage import FileSystemStorage, storages
1313
from django.utils.functional import LazyObject
14+
from django.utils.regex_helper import _lazy_re_compile
15+
16+
comment_re = _lazy_re_compile(r"\/\*[^*]*\*+([^/*][^*]*\*+)*\/", re.DOTALL)
17+
line_comment_re = _lazy_re_compile(
18+
r"\/\*[^*]*\*+([^/*][^*]*\*+)*\/|\/\/[^\n]*", re.DOTALL
19+
)
1420

1521

1622
class StaticFilesStorage(FileSystemStorage):
@@ -204,7 +210,22 @@ def url(self, name, force=False):
204210
"""
205211
return self._url(self.stored_name, name, force)
206212

207-
def url_converter(self, name, hashed_files, template=None):
213+
def get_comment_blocks(self, content, include_line_comments=False):
214+
"""
215+
Return a list of (start, end) tuples for each comment block.
216+
"""
217+
pattern = line_comment_re if include_line_comments else comment_re
218+
return [(match.start(), match.end()) for match in re.finditer(pattern, content)]
219+
220+
def is_in_comment(self, pos, comments):
221+
for start, end in comments:
222+
if start < pos and pos < end:
223+
return True
224+
if pos < start:
225+
return False
226+
return False
227+
228+
def url_converter(self, name, hashed_files, template=None, comment_blocks=None):
208229
"""
209230
Return the custom URL converter for the given file name.
210231
"""
@@ -222,6 +243,10 @@ def converter(matchobj):
222243
matched = matches["matched"]
223244
url = matches["url"]
224245

246+
# Ignore URLs in comments.
247+
if comment_blocks and self.is_in_comment(matchobj.start(), comment_blocks):
248+
return matched
249+
225250
# Ignore absolute/protocol-relative and data-uri URLs.
226251
if re.match(r"^[a-z]+:", url) or url.startswith("//"):
227252
return matched
@@ -375,7 +400,13 @@ def path_level(name):
375400
if matches_patterns(path, (extension,)):
376401
for pattern, template in patterns:
377402
converter = self.url_converter(
378-
name, hashed_files, template
403+
name,
404+
hashed_files,
405+
template,
406+
self.get_comment_blocks(
407+
content,
408+
include_line_comments=path.endswith(".js"),
409+
),
379410
)
380411
try:
381412
content = pattern.sub(converter, content)

docs/ref/contrib/staticfiles.txt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -349,12 +349,6 @@ argument. For example::
349349
manifest_storage = StaticFilesStorage(location=settings.BASE_DIR)
350350
super().__init__(*args, manifest_storage=manifest_storage, **kwargs)
351351

352-
.. admonition:: References in comments
353-
354-
``ManifestStaticFilesStorage`` doesn't ignore paths in statements that are
355-
commented out. This :ticket:`may crash on the nonexistent paths <21080>`.
356-
You should check and eventually strip comments.
357-
358352
.. attribute:: storage.ManifestStaticFilesStorage.manifest_hash
359353

360354
This attribute provides a single hash that changes whenever a file in the

tests/staticfiles_tests/project/documents/cached/css/ignored.css

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,24 @@ body {
88
background: url();
99
}
1010

11+
/* @import url("non_exist.css") */
12+
13+
/* url("non_exist.png") */
14+
15+
/*
16+
17+
@import url("non_exist.css")
18+
19+
url("non_exist.png")
20+
21+
@import url(other.css)
22+
23+
*/
24+
25+
body {
26+
background: #d3d6d8 /*url("does.not.exist.png")*/ url(/static/cached/img/relative.png);
27+
}
28+
29+
body {
30+
background: #d3d6d8 /* url("does.not.exist.png") */ url(/static/cached/img/relative.png) /*url("does.not.exist.either.png")*/;
31+
}

tests/staticfiles_tests/project/documents/cached/module.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,14 @@ export {
2424
firstVar as firstVarAlias,
2525
secondVar as secondVarAlias
2626
} from "./module_test.js";
27+
28+
// ignore block comments
29+
/* export * from "./module_test_missing.js"; */
30+
/*
31+
import rootConst from "/static/absolute_root_missing.js";
32+
const dynamicModule = import("./module_test_missing.js");
33+
*/
34+
35+
// ignore line comments
36+
// import testConst from "./module_test_missing.js";
37+
// const dynamicModule = import("./module_test_missing.js");

tests/staticfiles_tests/test_storage.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def test_template_tag_simple_content(self):
6565

6666
def test_path_ignored_completely(self):
6767
relpath = self.hashed_file_path("cached/css/ignored.css")
68-
self.assertEqual(relpath, "cached/css/ignored.55e7c226dda1.css")
68+
self.assertEqual(relpath, "cached/css/ignored.0e15ac4a4fb4.css")
6969
with storage.staticfiles_storage.open(relpath) as relfile:
7070
content = relfile.read()
7171
self.assertIn(b"#foobar", content)
@@ -75,6 +75,22 @@ def test_path_ignored_completely(self):
7575
self.assertIn(b"chrome:foobar", content)
7676
self.assertIn(b"//foobar", content)
7777
self.assertIn(b"url()", content)
78+
self.assertIn(b'/* @import url("non_exist.css") */', content)
79+
self.assertIn(b'/* url("non_exist.png") */', content)
80+
self.assertIn(b'@import url("non_exist.css")', content)
81+
self.assertIn(b'url("non_exist.png")', content)
82+
self.assertIn(b"@import url(other.css)", content)
83+
self.assertIn(
84+
b'background: #d3d6d8 /*url("does.not.exist.png")*/ '
85+
b'url("/static/cached/img/relative.acae32e4532b.png");',
86+
content,
87+
)
88+
self.assertIn(
89+
b'background: #d3d6d8 /* url("does.not.exist.png") */ '
90+
b'url("/static/cached/img/relative.acae32e4532b.png") '
91+
b'/*url("does.not.exist.either.png")*/',
92+
content,
93+
)
7894
self.assertPostCondition()
7995

8096
def test_path_with_querystring(self):
@@ -698,7 +714,7 @@ class TestCollectionJSModuleImportAggregationManifestStorage(CollectionTestCase)
698714

699715
def test_module_import(self):
700716
relpath = self.hashed_file_path("cached/module.js")
701-
self.assertEqual(relpath, "cached/module.4326210cf0bd.js")
717+
self.assertEqual(relpath, "cached/module.eaa407b94311.js")
702718
tests = [
703719
# Relative imports.
704720
b'import testConst from "./module_test.477bbebe77f0.js";',
@@ -721,6 +737,15 @@ def test_module_import(self):
721737
b" firstVar1 as firstVarAlias,\n"
722738
b" $second_var_2 as secondVarAlias\n"
723739
b'} from "./module_test.477bbebe77f0.js";',
740+
# Ignore block comments
741+
b'/* export * from "./module_test_missing.js"; */',
742+
b"/*\n"
743+
b'import rootConst from "/static/absolute_root_missing.js";\n'
744+
b'const dynamicModule = import("./module_test_missing.js");\n'
745+
b"*/",
746+
# Ignore line comments
747+
b'// import testConst from "./module_test_missing.js";',
748+
b'// const dynamicModule = import("./module_test_missing.js");',
724749
]
725750
with storage.staticfiles_storage.open(relpath) as relfile:
726751
content = relfile.read()
@@ -730,7 +755,7 @@ def test_module_import(self):
730755

731756
def test_aggregating_modules(self):
732757
relpath = self.hashed_file_path("cached/module.js")
733-
self.assertEqual(relpath, "cached/module.4326210cf0bd.js")
758+
self.assertEqual(relpath, "cached/module.eaa407b94311.js")
734759
tests = [
735760
b'export * from "./module_test.477bbebe77f0.js";',
736761
b'export { testConst } from "./module_test.477bbebe77f0.js";',

0 commit comments

Comments
 (0)