From 7624fdb9f8ea4cbff407436606998f572898038b Mon Sep 17 00:00:00 2001 From: msaelices Date: Mon, 17 Aug 2015 17:59:31 +0200 Subject: [PATCH] Fixed #25283 -- Fixed collectstatic crash if a URL contains a fragment with a path. A @font-face declaration may contain a fragment that looks like a relative path, e.g. @font-face { src: url('../fonts/font.svg#../path/like/fragment'); } In this case, an incorrect path was passed to the storage backend, which raised an error that caused collectstatic to crash. --- django/contrib/staticfiles/storage.py | 15 ++++++++++----- .../project/documents/cached/css/fragments.css | 1 + tests/staticfiles_tests/test_storage.py | 3 ++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index b3c53b72325..d6911c9fd7b 100644 --- a/django/contrib/staticfiles/storage.py +++ b/django/contrib/staticfiles/storage.py @@ -166,12 +166,15 @@ class HashedFilesMixin(object): name_parts = name.split(os.sep) # Using posix normpath here to remove duplicates url = posixpath.normpath(url) - url_parts = url.split('/') - parent_level, sub_level = url.count('..'), url.count('/') - if url.startswith('/'): + # Strip off the fragment so that a path-like fragment won't confuse + # the lookup. + url_path, fragment = urldefrag(url) + url_parts = url_path.split('/') + parent_level, sub_level = url_path.count('..'), url_path.count('/') + if url_path.startswith('/'): sub_level -= 1 url_parts = url_parts[1:] - if parent_level or not url.startswith('/'): + if parent_level or not url_path.startswith('/'): start, end = parent_level + 1, parent_level else: if sub_level: @@ -183,7 +186,9 @@ class HashedFilesMixin(object): joined_result = '/'.join(name_parts[:-start] + url_parts[end:]) hashed_url = self.url(unquote(joined_result), force=True) file_name = hashed_url.split('/')[-1:] - relative_url = '/'.join(url.split('/')[:-1] + file_name) + relative_url = '/'.join(url_path.split('/')[:-1] + file_name) + if fragment: + relative_url += '?#%s' % fragment if '?#' in url else '#%s' % fragment # Return the hashed version to the file return template % unquote(relative_url) diff --git a/tests/staticfiles_tests/project/documents/cached/css/fragments.css b/tests/staticfiles_tests/project/documents/cached/css/fragments.css index 540d54b88db..e6e70494651 100644 --- a/tests/staticfiles_tests/project/documents/cached/css/fragments.css +++ b/tests/staticfiles_tests/project/documents/cached/css/fragments.css @@ -1,6 +1,7 @@ @font-face { src: url('fonts/font.eot?#iefix') format('embedded-opentype'), url('fonts/font.svg#webfontIyfZbseF') format('svg'); + url('fonts/font.svg#../path/to/fonts/font.svg') format('svg'); url('data:font/woff;charset=utf-8;base64,d09GRgABAAAAADJoAA0AAAAAR2QAAQAAAAAAAAAAAAA'); } div { diff --git a/tests/staticfiles_tests/test_storage.py b/tests/staticfiles_tests/test_storage.py index b96d9d0f123..f0c69cebe66 100644 --- a/tests/staticfiles_tests/test_storage.py +++ b/tests/staticfiles_tests/test_storage.py @@ -85,11 +85,12 @@ class TestHashedFiles(object): def test_path_with_querystring_and_fragment(self): relpath = self.hashed_file_path("cached/css/fragments.css") - self.assertEqual(relpath, "cached/css/fragments.75433540b096.css") + self.assertEqual(relpath, "cached/css/fragments.ef92012a8c16.css") with storage.staticfiles_storage.open(relpath) as relfile: content = relfile.read() self.assertIn(b'fonts/font.a4b0478549d0.eot?#iefix', content) self.assertIn(b'fonts/font.b8d603e42714.svg#webfontIyfZbseF', content) + self.assertIn(b'fonts/font.b8d603e42714.svg#../path/to/fonts/font.svg', content) self.assertIn(b'data:font/woff;charset=utf-8;base64,d09GRgABAAAAADJoAA0AAAAAR2QAAQAAAAAAAAAAAAA', content) self.assertIn(b'#default#VML', content)